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); } } }