blob: 2e33d859f51913f19bae4176e2b16e90cba5d58f [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.cxf.fediz.core.metadata;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import org.w3c.dom.Document;
import org.apache.cxf.fediz.core.config.Claim;
import org.apache.cxf.fediz.core.config.FederationProtocol;
import org.apache.cxf.fediz.core.config.FedizContext;
import org.apache.cxf.fediz.core.config.Protocol;
import org.apache.cxf.fediz.core.config.SAMLProtocol;
import org.apache.cxf.fediz.core.exception.ProcessingException;
import org.apache.cxf.fediz.core.util.CertsUtils;
import org.apache.cxf.fediz.core.util.DOMUtils;
import org.apache.cxf.fediz.core.util.SignatureUtils;
import org.apache.cxf.fediz.core.util.StringUtils;
import org.apache.xml.security.stax.impl.util.IDGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static org.apache.cxf.fediz.core.FederationConstants.WS_FEDERATION_NS;
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;
public class MetadataWriter {
private static final Logger LOG = LoggerFactory.getLogger(MetadataWriter.class);
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
public Document getMetaData(
HttpServletRequest request, FedizContext config
) throws ProcessingException {
try (ByteArrayOutputStream bout = new ByteArrayOutputStream(4096)) {
Writer streamWriter = new OutputStreamWriter(bout, StandardCharsets.UTF_8);
XMLStreamWriter writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(streamWriter);
Protocol protocol = config.getProtocol();
writer.writeStartDocument("UTF-8", "1.0");
String referenceID = IDGenerator.generateID("_");
writer.writeStartElement("md", "EntityDescriptor", SAML2_METADATA_NS);
writer.writeAttribute("ID", referenceID);
String serviceURL = protocol.getApplicationServiceURL();
if (serviceURL == null) {
serviceURL = StringUtils.extractFullContextPath(request);
}
writer.writeAttribute("entityID", serviceURL);
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);
if (protocol instanceof FederationProtocol) {
writeFederationMetadata(writer, config, serviceURL);
} else if (protocol instanceof SAMLProtocol) {
writeSAMLMetadata(writer, request, config, serviceURL);
}
writer.writeEndElement(); // EntityDescriptor
writer.writeEndDocument();
streamWriter.flush();
bout.flush();
//
if (LOG.isDebugEnabled()) {
String out = new String(bout.toByteArray());
LOG.debug("***************** unsigned ****************");
LOG.debug(out);
LOG.debug("***************** unsigned ****************");
}
boolean hasSigningKey = false;
try {
if (config.getSigningKey().getCrypto() != null) {
hasSigningKey = true;
}
} catch (Exception ex) {
LOG.info("No signingKey element found in config: " + ex.getMessage());
}
try (InputStream is = new ByteArrayInputStream(bout.toByteArray())) {
if (hasSigningKey) {
Document doc = DOMUtils.readXml(is);
Document result = SignatureUtils.signMetaInfo(
config.getSigningKey().getCrypto(), config.getSigningKey().getKeyAlias(),
config.getSigningKey().getKeyPassword(), doc, referenceID);
if (result != null) {
return result;
} else {
throw new ProcessingException("Failed to sign the metadata document: result=null");
}
}
return DOMUtils.readXml(is);
}
} catch (ProcessingException e) {
throw e;
} catch (Exception e) {
LOG.error("Error creating service metadata information ", e);
throw new ProcessingException("Error creating service metadata information: " + e.getMessage());
}
}
private void writeFederationMetadata(
XMLStreamWriter writer,
FedizContext config,
String serviceURL
) throws XMLStreamException {
writer.writeStartElement("md", "RoleDescriptor", WS_FEDERATION_NS);
writer.writeAttribute(SCHEMA_INSTANCE_NS, "type", "fed:ApplicationServiceType");
writer.writeAttribute("protocolSupportEnumeration", WS_FEDERATION_NS);
writer.writeStartElement("fed", "ApplicationServiceEndpoint", WS_FEDERATION_NS);
writer.writeStartElement("wsa", "EndpointReference", WS_ADDRESSING_NS);
writer.writeStartElement("wsa", "Address", WS_ADDRESSING_NS);
writer.writeCharacters(serviceURL);
writer.writeEndElement(); // Address
writer.writeEndElement(); // EndpointReference
writer.writeEndElement(); // ApplicationServiceEndpoint
// create target scope element
writer.writeStartElement("fed", "TargetScope", WS_FEDERATION_NS);
List<String> audienceUris = config.getAudienceUris();
if (audienceUris != null) {
for (String uri : audienceUris) {
writer.writeStartElement("wsa", "EndpointReference", WS_ADDRESSING_NS);
writer.writeStartElement("wsa", "Address", WS_ADDRESSING_NS);
writer.writeCharacters(uri);
writer.writeEndElement(); // Address
writer.writeEndElement(); // EndpointReference
}
}
writer.writeEndElement(); // TargetScope
FederationProtocol protocol = (FederationProtocol)config.getProtocol();
List<Claim> claims = protocol.getClaimTypesRequested();
if (claims != null && !claims.isEmpty()) {
// create ClaimsType section
writer.writeStartElement("fed", "ClaimTypesRequested", WS_FEDERATION_NS);
for (Claim claim : claims) {
writer.writeStartElement("auth", "ClaimType", WS_FEDERATION_NS);
writer.writeAttribute("Uri", claim.getType());
if (claim.isOptional()) {
writer.writeAttribute("Optional", "true");
} else {
writer.writeAttribute("Optional", "false");
}
writer.writeEndElement(); // ClaimType
}
writer.writeEndElement(); // ClaimsTypeRequested
}
// create sign in endpoint section
writer.writeStartElement("fed", "PassiveRequestorEndpoint", WS_FEDERATION_NS);
writer.writeStartElement("wsa", "EndpointReference", WS_ADDRESSING_NS);
writer.writeStartElement("wsa", "Address", WS_ADDRESSING_NS);
writer.writeCharacters(serviceURL);
// writer.writeCharacters("http://host:port/url Issuer from config");
writer.writeEndElement(); // Address
writer.writeEndElement(); // EndpointReference
writer.writeEndElement(); // PassiveRequestorEndpoint
writer.writeEndElement(); // RoleDescriptor
}
private void writeSAMLMetadata(
XMLStreamWriter writer,
HttpServletRequest request,
FedizContext config,
String serviceURL
) throws Exception {
SAMLProtocol protocol = (SAMLProtocol)config.getProtocol();
writer.writeStartElement("md", "SPSSODescriptor", SAML2_METADATA_NS);
writer.writeAttribute("AuthnRequestsSigned", Boolean.toString(protocol.isSignRequest()));
writer.writeAttribute("WantAssertionsSigned", "true");
writer.writeAttribute("protocolSupportEnumeration", "urn:oasis:names:tc:SAML:2.0:protocol");
if (config.getLogoutURL() != null) {
writer.writeStartElement("md", "SingleLogoutService", SAML2_METADATA_NS);
String logoutURL = config.getLogoutURL();
if (logoutURL.startsWith("/")) {
logoutURL = StringUtils.extractFullContextPath(request).concat(logoutURL.substring(1));
} else {
logoutURL = StringUtils.extractFullContextPath(request).concat(logoutURL);
}
writer.writeAttribute("Location", logoutURL);
writer.writeAttribute("Binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
writer.writeEndElement(); // SingleLogoutService
}
writer.writeStartElement("md", "AssertionConsumerService", SAML2_METADATA_NS);
writer.writeAttribute("Location", serviceURL);
writer.writeAttribute("index", "0");
writer.writeAttribute("isDefault", "true");
writer.writeAttribute("Binding", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
writer.writeEndElement(); // AssertionConsumerService
if (protocol.getClaimTypesRequested() != null && !protocol.getClaimTypesRequested().isEmpty()) {
writer.writeStartElement("md", "AttributeConsumingService", SAML2_METADATA_NS);
writer.writeAttribute("index", "0");
writer.writeStartElement("md", "ServiceName", SAML2_METADATA_NS);
writer.writeAttribute("xml:lang", "en");
writer.writeCharacters(config.getName());
writer.writeEndElement(); // ServiceName
for (Claim claim : protocol.getClaimTypesRequested()) {
writer.writeStartElement("md", "RequestedAttribute", SAML2_METADATA_NS);
writer.writeAttribute("isRequired", Boolean.toString(claim.isOptional()));
writer.writeAttribute("Name", claim.getType());
writer.writeAttribute("NameFormat",
"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified");
writer.writeEndElement(); // RequestedAttribute
}
writer.writeEndElement(); // AttributeConsumingService
}
boolean hasSigningKey = false;
try {
if (config.getSigningKey().getCrypto() != null) {
hasSigningKey = true;
}
} catch (Exception ex) {
LOG.info("No signingKey element found in config: " + ex.getMessage());
}
if (protocol.isSignRequest() && hasSigningKey) {
writer.writeStartElement("md", "KeyDescriptor", SAML2_METADATA_NS);
writer.writeAttribute("use", "signing");
writer.writeStartElement("ds", "KeyInfo", "http://www.w3.org/2000/09/xmldsig#");
writer.writeNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
writer.writeStartElement("ds", "X509Data", "http://www.w3.org/2000/09/xmldsig#");
writer.writeStartElement("ds", "X509Certificate", "http://www.w3.org/2000/09/xmldsig#");
// Write the Base-64 encoded certificate
String keyAlias = config.getSigningKey().getKeyAlias();
if (keyAlias == null || "".equals(keyAlias)) {
keyAlias = config.getSigningKey().getCrypto().getDefaultX509Identifier();
}
X509Certificate cert =
CertsUtils.getX509CertificateFromCrypto(config.getSigningKey().getCrypto(), keyAlias);
if (cert == null) {
throw new ProcessingException(
"No signing certs were found to insert into the metadata using name: "
+ keyAlias);
}
byte[] data = cert.getEncoded();
String encodedCertificate = Base64.getEncoder().encodeToString(data);
writer.writeCharacters(encodedCertificate);
writer.writeEndElement(); // X509Certificate
writer.writeEndElement(); // X509Data
writer.writeEndElement(); // KeyInfo
writer.writeEndElement(); // KeyDescriptor
}
writer.writeEndElement(); // SPSSODescriptor
}
}