package org.apereo.cas.support.saml.web.idp.metadata;
import com.google.common.base.Throwables;
import net.shibboleth.utilities.java.support.security.SelfSignedCertificateGenerator;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.saml.idp.SamlIdPProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import javax.annotation.PostConstruct;
import java.io.File;
import java.io.StringWriter;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* A metadata generator based on a predefined template.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class TemplatedMetadataAndCertificatesGenerationService implements SamlIdpMetadataAndCertificatesGenerationService {
private static final String URI_SUBJECT_ALTNAME_POSTFIX = "/idp/metadata";
private static final String BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----";
private static final String END_CERTIFICATE = "-----END CERTIFICATE-----";
private static final Logger LOGGER = LoggerFactory.getLogger(TemplatedMetadataAndCertificatesGenerationService.class);
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
private ResourceLoader resourceLoader;
/**
* Initializes a new Generate saml metadata.
*/
@PostConstruct
public void initialize() {
try {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
final Resource metadataLocation = idp.getMetadata().getLocation();
if (!metadataLocation.exists()) {
LOGGER.debug("Metadata directory [{}] does not exist. Creating...", metadataLocation);
if (!metadataLocation.getFile().mkdir()) {
throw new IllegalArgumentException("Metadata directory location " + metadataLocation + " cannot be located/created");
}
}
LOGGER.info("Metadata directory location is at [{}] with entityID [{}]", metadataLocation, idp.getEntityId());
performGenerationSteps();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Is metadata missing?
*
* @return true/false
*/
public boolean isMetadataMissing() {
try {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
return !idp.getMetadata().getMetadataFile().exists();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
@Override
public File performGenerationSteps() {
try {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
LOGGER.debug("Preparing to generate metadata for entityId [{}]", idp.getEntityId());
if (isMetadataMissing()) {
LOGGER.info("Metadata does not exist at [{}]. Creating...", idp.getMetadata().getMetadataFile());
LOGGER.info("Creating self-sign certificate for signing...");
buildSelfSignedSigningCert();
LOGGER.info("Creating self-sign certificate for encryption...");
buildSelfSignedEncryptionCert();
LOGGER.info("Creating metadata...");
buildMetadataGeneratorParameters();
}
LOGGER.info("Metadata is available at [{}]", idp.getMetadata().getMetadataFile());
return idp.getMetadata().getMetadataFile();
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
throw Throwables.propagate(e);
}
}
private String getIdPEndpointUrl() {
return casProperties.getServer().getPrefix().concat("/idp");
}
private String getIdPHostName() {
try {
final URL url = new URL(casProperties.getServer().getPrefix());
return url.getHost();
} catch (final Exception e) {
throw Throwables.propagate(e);
}
}
/**
* Build self signed encryption cert.
*
* @throws Exception the exception
*/
protected void buildSelfSignedEncryptionCert() throws Exception {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
final SelfSignedCertificateGenerator generator = new SelfSignedCertificateGenerator();
generator.setHostName(getIdPHostName());
generator.setCertificateFile(idp.getMetadata().getEncryptionCertFile().getFile());
generator.setPrivateKeyFile(idp.getMetadata().getEncryptionKeyFile().getFile());
generator.setURISubjectAltNames(Arrays.asList(getIdPHostName().concat(URI_SUBJECT_ALTNAME_POSTFIX)));
generator.generate();
}
/**
* Build self signed signing cert.
*
* @throws Exception the exception
*/
protected void buildSelfSignedSigningCert() throws Exception {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
final SelfSignedCertificateGenerator generator = new SelfSignedCertificateGenerator();
generator.setHostName(getIdPHostName());
generator.setCertificateFile(idp.getMetadata().getSigningCertFile().getFile());
generator.setPrivateKeyFile(idp.getMetadata().getSigningKeyFile().getFile());
generator.setURISubjectAltNames(Arrays.asList(getIdPHostName().concat(URI_SUBJECT_ALTNAME_POSTFIX)));
generator.generate();
}
/**
* Build metadata generator parameters by passing the encryption,
* signing and back-channel certs to the parameter generator.
*
* @throws Exception Thrown if cert files are missing, or metadata file inaccessible.
*/
protected void buildMetadataGeneratorParameters() throws Exception {
final SamlIdPProperties idp = casProperties.getAuthn().getSamlIdp();
final Resource template = this.resourceLoader.getResource("classpath:/template-idp-metadata.xml");
String signingKey = FileUtils.readFileToString(idp.getMetadata().getSigningCertFile().getFile(), StandardCharsets.UTF_8);
signingKey = StringUtils.remove(signingKey, BEGIN_CERTIFICATE);
signingKey = StringUtils.remove(signingKey, END_CERTIFICATE).trim();
String encryptionKey = FileUtils.readFileToString(idp.getMetadata().getEncryptionCertFile().getFile(), StandardCharsets.UTF_8);
encryptionKey = StringUtils.remove(encryptionKey, BEGIN_CERTIFICATE);
encryptionKey = StringUtils.remove(encryptionKey, END_CERTIFICATE).trim();
try (StringWriter writer = new StringWriter()) {
IOUtils.copy(template.getInputStream(), writer, StandardCharsets.UTF_8);
final String metadata = writer.toString()
.replace("${entityId}", idp.getEntityId())
.replace("${scope}", idp.getScope())
.replace("${idpEndpointUrl}", getIdPEndpointUrl())
.replace("${encryptionKey}", encryptionKey)
.replace("${signingKey}", signingKey);
FileUtils.write(idp.getMetadata().getMetadataFile(), metadata, StandardCharsets.UTF_8);
}
}
}