package org.apereo.cas.ws.idp.metadata; import com.google.common.base.Throwables; import org.apache.commons.lang3.StringUtils; import org.apache.cxf.fediz.core.util.CertsUtils; import org.apache.cxf.fediz.core.util.SignatureUtils; import org.apache.cxf.staxutils.W3CDOMStreamWriter; import org.apache.wss4j.common.crypto.Crypto; import org.apache.wss4j.common.crypto.CryptoFactory; import org.apache.wss4j.common.util.DOM2Writer; import org.apache.xml.security.stax.impl.util.IDGenerator; import org.apache.xml.security.utils.Base64; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.support.wsfed.WsFederationProperties; import org.apereo.cas.support.util.CryptoUtils; import org.apereo.cas.ws.idp.WSFederationClaims; import org.apereo.cas.ws.idp.WSFederationConstants; import org.jooq.lambda.Unchecked; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import javax.xml.stream.XMLStreamWriter; import java.nio.charset.StandardCharsets; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Properties; import static org.apache.cxf.fediz.core.FedizConstants.SAML2_METADATA_NS; import static org.apache.cxf.fediz.core.FedizConstants.SCHEMA_INSTANCE_NS; import static org.apache.cxf.fediz.core.FedizConstants.WS_ADDRESSING_NS; import static org.apache.cxf.fediz.core.FedizConstants.WS_FEDERATION_NS; /** * This is {@link WSFederationMetadataWriter}. * * @author Misagh Moayyed * @since 5.1.0 */ public class WSFederationMetadataWriter { private static final Logger LOGGER = LoggerFactory.getLogger(WSFederationMetadataWriter.class); protected WSFederationMetadataWriter() { } /** * Produce metadata document. * * @param config the config * @return the document */ public static Document produceMetadataDocument(final CasConfigurationProperties config) { try { final WsFederationProperties.SecurityTokenService sts = config.getAuthn().getWsfedIdP().getSts(); final Properties prop = CryptoUtils.getSecurityProperties(sts.getRealm().getKeystoreFile(), sts.getRealm().getKeystorePassword(), sts.getRealm().getKeystoreAlias()); final Crypto crypto = CryptoFactory.getInstance(prop); final W3CDOMStreamWriter writer = new W3CDOMStreamWriter(); writer.writeStartDocument(StandardCharsets.UTF_8.name(), "1.0"); final String referenceID = IDGenerator.generateID("_"); writer.writeStartElement("md", "EntityDescriptor", SAML2_METADATA_NS); writer.writeAttribute("ID", referenceID); final String idpEntityId = config.getServer().getPrefix().concat(WSFederationConstants.ENDPOINT_FEDERATION_REQUEST); writer.writeAttribute("entityID", idpEntityId); writer.writeNamespace("md", SAML2_METADATA_NS); writer.writeNamespace("fed", WS_FEDERATION_NS); writer.writeNamespace("wsa", WS_ADDRESSING_NS); writer.writeNamespace("auth", WS_FEDERATION_NS); writer.writeNamespace("xsi", SCHEMA_INSTANCE_NS); final String stsUrl = config.getServer().getPrefix().concat(WSFederationConstants.ENDPOINT_STS) .concat(config.getAuthn().getWsfedIdP().getIdp().getRealmName()); writeFederationMetadata(writer, idpEntityId, stsUrl, crypto); writer.writeEndElement(); writer.writeEndDocument(); writer.close(); final String out = DOM2Writer.nodeToString(writer.getDocument()); LOGGER.debug("Produced unsigned metadata"); LOGGER.debug(out); final Document result = SignatureUtils.signMetaInfo(crypto, null, config.getAuthn().getWsfedIdP().getSts().getRealm().getKeyPassword(), writer.getDocument(), referenceID); if (result != null) { return result; } throw new RuntimeException("Failed to sign the metadata document"); } catch (final Exception e) { throw new RuntimeException("Error creating service metadata information: " + e.getMessage(), e); } } private static void writeFederationMetadata(final XMLStreamWriter writer, final String idpEntityId, final String ststUrl, final Crypto crypto) throws Exception { writer.writeStartElement("md", "RoleDescriptor", WS_FEDERATION_NS); writer.writeAttribute(SCHEMA_INSTANCE_NS, "type", "fed:SecurityTokenServiceType"); writer.writeAttribute("protocolSupportEnumeration", WS_FEDERATION_NS); writer.writeStartElement(StringUtils.EMPTY, "KeyDescriptor", SAML2_METADATA_NS); writer.writeAttribute("use", "signing"); writer.writeStartElement(StringUtils.EMPTY, "KeyInfo", "http://www.w3.org/2000/09/xmldsig#"); writer.writeStartElement(StringUtils.EMPTY, "X509Data", "http://www.w3.org/2000/09/xmldsig#"); writer.writeStartElement(StringUtils.EMPTY, "X509Certificate", "http://www.w3.org/2000/09/xmldsig#"); try { final String keyAlias = crypto.getDefaultX509Identifier(); final X509Certificate cert = CertsUtils.getX509CertificateFromCrypto(crypto, keyAlias); writer.writeCharacters(Base64.encode(cert.getEncoded())); } catch (final Exception ex) { LOGGER.error("Failed to add certificate information to metadata. Metadata incomplete", ex); throw Throwables.propagate(ex); } writer.writeEndElement(); writer.writeEndElement(); writer.writeEndElement(); writer.writeEndElement(); writer.writeStartElement("fed", "SecurityTokenServiceEndpoint", WS_FEDERATION_NS); writer.writeStartElement("wsa", "EndpointReference", WS_ADDRESSING_NS); writer.writeStartElement("wsa", "Address", WS_ADDRESSING_NS); writer.writeCharacters(ststUrl); writer.writeEndElement(); writer.writeEndElement(); writer.writeEndElement(); writer.writeStartElement("fed", "PassiveRequestorEndpoint", WS_FEDERATION_NS); writer.writeStartElement("wsa", "EndpointReference", WS_ADDRESSING_NS); writer.writeStartElement("wsa", "Address", WS_ADDRESSING_NS); writer.writeCharacters(idpEntityId); writer.writeEndElement(); writer.writeEndElement(); writer.writeEndElement(); writer.writeStartElement("fed", "ClaimTypesOffered", WS_FEDERATION_NS); Arrays.stream(WSFederationClaims.values()).forEach(Unchecked.consumer(claim -> { writer.writeStartElement("auth", "ClaimType", WS_FEDERATION_NS); writer.writeAttribute("Uri", claim.getUri()); writer.writeAttribute("Optional", "true"); writer.writeEndElement(); })); writer.writeEndElement(); writer.writeEndElement(); } }