diff --git a/modules/adfs/lib/IdP/ADFS.php b/modules/adfs/lib/IdP/ADFS.php
index 691480b7bf5e20a8cc8568688b1be3b4218c315d..0989d11735d49477bc1afa94b24844b21fa620d1 100644
--- a/modules/adfs/lib/IdP/ADFS.php
+++ b/modules/adfs/lib/IdP/ADFS.php
@@ -4,6 +4,8 @@ namespace SimpleSAML\Module\adfs\IdP;
 
 use RobRichards\XMLSecLibs\XMLSecurityDSig;
 use RobRichards\XMLSecLibs\XMLSecurityKey;
+use SimpleSAML\Utils\Config\Metadata;
+use SimpleSAML\Utils\Crypto;
 
 class ADFS
 {
@@ -143,6 +145,132 @@ MSG;
         $t->show();
     }
 
+
+    /**
+     * Get the metadata of a given hosted ADFS IdP.
+     *
+     * @param string $entityid The entity ID of the hosted ADFS IdP whose metadata we want to fetch.
+     *
+     * @return array
+     * @throws \SimpleSAML\Error\Exception
+     * @throws \SimpleSAML\Error\MetadataNotFound
+     */
+    public static function getHostedMetadata($entityid)
+    {
+        $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
+        $config = $handler->getMetaDataConfig($entityid, 'adfs-idp-hosted');
+
+        $endpoint = \SimpleSAML\Module::getModuleURL('adfs/idp/prp.php');
+        $metadata = [
+            'metadata-set' => 'adfs-idp-hosted',
+            'entityid' => $entityid,
+            'SingleSignOnService' => [
+                [
+                    'Binding' => \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                    'Location' => $endpoint,
+                ]
+            ],
+            'SingleLogoutService' => [
+                'Binding' => \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                'Location' => $endpoint,
+            ],
+            'NameIDFormat' => $config->getString('NameIDFormat', \SAML2\Constants::NAMEID_TRANSIENT),
+            'contacts' => [],
+        ];
+
+        // add certificates
+        $keys = [];
+        $certInfo = Crypto::loadPublicKey($config, false, 'new_');
+        $hasNewCert = false;
+        if ($certInfo !== null) {
+            $keys[] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => true,
+                'X509Certificate' => $certInfo['certData'],
+                'prefix' => 'new_',
+            ];
+            $hasNewCert = true;
+        }
+
+        $certInfo = Crypto::loadPublicKey($config, true);
+        $keys[] = [
+            'type' => 'X509Certificate',
+            'signing' => true,
+            'encryption' => $hasNewCert === false,
+            'X509Certificate' => $certInfo['certData'],
+            'prefix' => '',
+        ];
+
+        if ($config->hasValue('https.certificate')) {
+            $httpsCert = Crypto::loadPublicKey($config, true, 'https.');
+            $keys[] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => false,
+                'X509Certificate' => $httpsCert['certData'],
+                'prefix' => 'https.'
+            ];
+        }
+        $metadata['keys'] = $keys;
+
+        // add organization information
+        if ($config->hasValue('OrganizationName')) {
+            $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName');
+            $metadata['OrganizationDisplayName'] = $config->getLocalizedString(
+                'OrganizationDisplayName',
+                $metadata['OrganizationName']
+            );
+
+            if (!$config->hasValue('OrganizationURL')) {
+                throw new \SimpleSAMl\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.');
+            }
+            $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL');
+        }
+
+        // add scope
+        if ($config->hasValue('scope')) {
+            $metadata['scope'] = $config->getArray('scope');
+        }
+
+        // add extensions
+        if ($config->hasValue('EntityAttributes')) {
+            $metadata['EntityAttributes'] = $config->getArray('EntityAttributes');
+
+            // check for entity categories
+            if (Metadata::isHiddenFromDiscovery($metadata)) {
+                $metadata['hide.from.discovery'] = true;
+            }
+        }
+
+        if ($config->hasValue('UIInfo')) {
+            $metadata['UIInfo'] = $config->getArray('UIInfo');
+        }
+
+        if ($config->hasValue('DiscoHints')) {
+            $metadata['DiscoHints'] = $config->getArray('DiscoHints');
+        }
+
+        if ($config->hasValue('RegistrationInfo')) {
+            $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo');
+        }
+
+        // add contact information
+        $globalConfig = \SimpleSAML\Configuration::getInstance();
+        $email = $globalConfig->getString('technicalcontact_email', false);
+        if ($email && $email !== 'na@example.org') {
+            $contact = [
+                'emailAddress' => $email,
+                'name' => $globalConfig->getString('technicalcontact_name', null),
+                'contactType' => 'technical',
+            ];
+            $metadata['contacts'][] = Metadata::getContact($contact);
+        }
+
+        return $metadata;
+    }
+
+
     public static function sendResponse(array $state)
     {
         $spMetadata = $state["SPMetadata"];
diff --git a/modules/saml/lib/Auth/Source/SP.php b/modules/saml/lib/Auth/Source/SP.php
index b2a0f5824d28686bff12dab94105aee082c6747a..9187ed846df8b77813ac994018aa381b0bff957f 100644
--- a/modules/saml/lib/Auth/Source/SP.php
+++ b/modules/saml/lib/Auth/Source/SP.php
@@ -42,6 +42,14 @@ class SP extends Source
      */
     private $disable_scoping;
 
+    /**
+     * A list of supported protocols.
+     *
+     * @var array
+     */
+    private $protocols = [];
+
+
     /**
      * Constructor for SAML SP authentication source.
      *
@@ -98,16 +106,161 @@ class SP extends Source
         return $this->entityId;
     }
 
+
     /**
-     * Retrieve the metadata of this SP.
+     * Retrieve the metadata array of this SP, as a remote IdP would see it.
      *
-     * @return \SimpleSAML\Configuration  The metadata of this SP.
+     * @return array The metadata array for its use by a remote IdP.
      */
-    public function getMetadata()
+    public function getHostedMetadata()
     {
-        return $this->metadata;
+        $entityid = $this->getEntityId();
+        $metadata = [
+            'entityid' => $entityid,
+            'metadata-set' => 'smal20-sp-remote',
+            'SingleLogoutService' => $this->getSLOEndpoints(),
+            'AssertionConsumerService' => $this->getACSEndpoints(),
+        ];
+
+        // add NameIDPolicy
+        if ($this->metadata->hasValue('NameIDValue')) {
+            $format = $this->metadata->getValue('NameIDPolicy');
+            if (is_array($format)) {
+                $metadata['NameIDFormat'] = \SimpleSAML\Configuration::loadFromArray($format)->getString(
+                    'Format',
+                    \SAML2\Constants::NAMEID_TRANSIENT
+                );
+            } elseif (is_string($format)) {
+                $metadata['NameIDFormat'] = $format;
+            }
+        }
+
+        // add attributes
+        $name = $this->metadata->getLocalizedString('name', null);
+        $attributes = $this->metadata->getArray('attributes', []);
+        if ($name !== null) {
+            if (!empty($attributes)) {
+                $metadata['name'] = $name;
+                $metadata['attributes'] = $attributes;
+                if ($this->metadata->hasValue('attributes.required')) {
+                    $metadata['attributes.required'] = $this->metadata->getArray('attributes.required');
+                }
+                if ($this->metadata->hasValue('description')) {
+                    $metadata['description'] = $this->metadata->getArray('description');
+                }
+                if ($this->metadata->hasValue('attributes.NameFormat')) {
+                    $metadata['attributes.NameFormat'] = $this->metadata->getString('attributes.NameFormat');
+                }
+                if ($this->metadata->hasValue('attributes.index')) {
+                    $metadata['attributes.index'] = $this->metadata->getInteger('attributes.index');
+                }
+                if ($this->metadata->hasValue('attributes.isDefault')) {
+                    $metadata['attributes.isDefault'] = $this->metadata->getBoolean('attributes.isDefault');
+                }
+            }
+        }
+
+        // add organization info
+        $org = $this->metadata->getLocalizedString('OrganizationName', null);
+        if ($org !== null) {
+            $metadata['OrganizationName'] = $org;
+            $metadata['OrganizationDisplayName'] = $this->metadata->getLocalizedString('OrganizationDisplayName', $org);
+            $metadata['OrganizationURL'] = $this->metadata->getLocalizedString('OrganizationURL', null);
+            if ($metadata['OrganizationURL'] === null) {
+                throw new \SimpleSAML\Error\Exception(
+                    'If OrganizationName is set, OrganizationURL must also be set.'
+                );
+            }
+        }
+
+        // add contacts
+        $contacts = $this->metadata->getArray('contact', []);
+        foreach ($contacts as $contact) {
+            $metadata['contacts'][] = \SimpleSAML\Utils\Config\Metadata::getContact($contact);
+        }
+
+        // add technical contact
+        $globalConfig = \SimpleSAML\Configuration::getInstance();
+        $email = $globalConfig->getString('technicalcontact_email', 'na@example.org');
+        if ($email && $email !== 'na@example.org') {
+            $contact = [
+                'emailAddress' => $email,
+                'name' => $globalConfig->getString('technicalcontact_name', null),
+                'contactType' => 'technical',
+            ];
+            $metadata['contacts'][] = \SimpleSAML\Utils\Config\Metadata::getContact($contact);
+        }
+
+        // add certificate(s)
+        $certInfo = \SimpleSAML\Utils\Crypto::loadPublicKey($this->metadata, false, 'new_');
+        $hasNewCert = false;
+        if ($certInfo !== null && array_key_exists('certData', $certInfo)) {
+            $hasNewCert = true;
+            $metadata['keys'][] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => true,
+                'X509Certificate' => $certInfo['certData'],
+                'prefix' => 'new_',
+                'url' => \SimpleSAML\Module::getModuleURL(
+                    'admin/cert',
+                    [
+                        'sp' => $this->getAuthId(),
+                        'prefix' => 'new_'
+                    ]
+                ),
+                'name' => 'sp',
+            ];
+        }
+
+        $certInfo = \SimpleSAML\Utils\Crypto::loadPublicKey($this->metadata);
+        if ($certInfo !== null && array_key_exists('certData', $certInfo)) {
+            $metadata['keys'][] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => $hasNewCert ? false : true,
+                'X509Certificate' => $certInfo['certData'],
+                'prefix' => '',
+                'url' => \SimpleSAML\Module::getModuleURL(
+                    'admin/cert',
+                    [
+                        'sp' => $this->getAuthId(),
+                        'prefix' => ''
+                    ]
+                ),
+                'name' => 'sp',
+            ];
+        }
+
+        // add EntityAttributes extension
+        if ($this->metadata->hasValue('EntityAttributes')) {
+            $metadata['EntityAttributes'] = $this->metadata->getArray('EntityAttributes');
+        }
+
+        // add UIInfo extension
+        if ($this->metadata->hasValue('UIInfo')) {
+            $metadata['UIInfo'] = $this->metadata->getArray('UIInfo');
+        }
+
+        // add RegistrationInfo extension
+        if ($this->metadata->hasValue('RegistrationInfo')) {
+            $metadata['RegistrationInfo'] = $this->metadata->getArray('RegistrationInfo');
+        }
+
+        // add signature options
+        if ($this->metadata->hasValue('WantAssertiosnsSigned')) {
+            $metadata['saml20.sign.assertion'] = $this->metadata->getBoolean('WantAssertionsSigned');
+        }
+        if ($this->metadata->hasValue('redirect.sign')) {
+            $metadata['redirect.validate'] = $this->metadata->getBoolean('redirect.sign');
+        } elseif ($this->metadata->hasValue('sign.authnrequest')) {
+            $metadata['validate.authnrequest'] = $this->metadata->getBoolean('sign.authnrequest');
+        }
+
+        return $metadata;
     }
 
+
     /**
      * Retrieve the metadata of an IdP.
      *
@@ -146,6 +299,142 @@ class SP extends Source
             var_export($entityId, true));
     }
 
+
+    /**
+     * Retrieve the metadata of this SP.
+     *
+     * @return \SimpleSAML\Configuration  The metadata of this SP.
+     */
+    public function getMetadata()
+    {
+        return $this->metadata;
+    }
+
+
+    /**
+     * Get a list with the protocols supported by this SP.
+     *
+     * @return array
+     */
+    public function getSupportedProtocols()
+    {
+        return $this->protocols;
+    }
+
+
+    /**
+     * Get the AssertionConsumerService endpoints for a given local SP.
+     *
+     * @return array
+     * @throws \Exception
+     */
+    private function getACSEndpoints()
+    {
+        $endpoints = [];
+        $default = [
+            \SAML2\Constants::BINDING_HTTP_POST,
+            'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+            \SAML2\Constants::BINDING_HTTP_ARTIFACT,
+            'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
+        ];
+        if ($this->metadata->getString('ProtocolBinding', '') === \SAML2\Constants::BINDING_HOK_SSO) {
+            $default[] = \SAML2\Constants::BINDING_HOK_SSO;
+        }
+
+        $bindings = $this->metadata->getArray('acs.Bindings', $default);
+        $index = 0;
+        foreach ($bindings as $service) {
+            switch ($service) {
+                case \SAML2\Constants::BINDING_HTTP_POST:
+                    $acs = [
+                        'Binding' => \SAML2\Constants::BINDING_HTTP_POST,
+                        'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()),
+                    ];
+                    if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) {
+                        $this->protocols[] = \SAML2\Constants::NS_SAMLP;
+                    }
+                    break;
+                case 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post':
+                    $acs = [
+                        'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post',
+                        'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml1-acs.php/'.$this->getAuthId()),
+                    ];
+                    if (!in_array('urn:oasis:names:tc:SAML:1.0:profiles:browser-post', $this->protocols, true)) {
+                        $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
+                    }
+                    break;
+                case \SAML2\Constants::BINDING_HTTP_ARTIFACT:
+                    $acs = [
+                        'Binding' => \SAML2\Constants::BINDING_HTTP_ARTIFACT,
+                        'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()),
+                    ];
+                    if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) {
+                        $this->protocols[] = \SAML2\Constants::NS_SAMLP;
+                    }
+                    break;
+                case 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01':
+                    $acs = [
+                        'Binding' => 'urn:oasis:names:tc:SAML:1.0:profiles:artifact-01',
+                        'Location' => \SimpleSAML\Module::getModuleURL(
+                            'saml/sp/saml1-acs.php/'.$this->getAuthId().'/artifact'
+                        ),
+                    ];
+                    if (!in_array('urn:oasis:names:tc:SAML:1.1:protocol', $this->protocols, true)) {
+                        $this->protocols[] = 'urn:oasis:names:tc:SAML:1.1:protocol';
+                    }
+                    break;
+                case \SAML2\Constants::BINDING_HOK_SSO:
+                    $acs = [
+                        'Binding' => \SAML2\Constants::BINDING_HOK_SSO,
+                        'Location' => \SimpleSAML\Module::getModuleURL('saml/sp/saml2-acs.php/'.$this->getAuthId()),
+                        'hoksso:ProtocolBinding' => \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                    ];
+                    if (!in_array(\SAML2\Constants::NS_SAMLP, $this->protocols, true)) {
+                        $this->protocols[] = \SAML2\Constants::NS_SAMLP;
+                    }
+                    break;
+            }
+            $acs['index'] = $index;
+            $endpoints[] = $acs;
+            $index++;
+        }
+        return $endpoints;
+    }
+
+
+    /**
+     * Get the SingleLogoutService endpoints available for a given local SP.
+     *
+     * @return array
+     * @throws \SimpleSAML\Error\CriticalConfigurationError
+     */
+    private function getSLOEndpoints()
+    {
+        $store = \SimpleSAML\Store::getInstance();
+        $bindings = $this->metadata->getArray(
+            'SingleLogoutServiceBinding',
+            [
+                \SAML2\Constants::BINDING_HTTP_REDIRECT,
+                \SAML2\Constants::BINDING_SOAP,
+            ]
+        );
+        $location = \SimpleSAML\Module::getModuleURL('saml/sp/saml2-logout.php/'.$this->getAuthId());
+
+        $endpoints = [];
+        foreach ($bindings as $binding) {
+            if ($binding == \SAML2\Constants::BINDING_SOAP && !($store instanceof \SimpleSAML\Store\SQL)) {
+                // we cannot properly support SOAP logout
+                continue;
+            }
+            $endpoints[] = [
+                'Binding' => $binding,
+                'Location' => $location,
+            ];
+        }
+        return $endpoints;
+    }
+
+
     /**
      * Send a SAML1 SSO request to an IdP.
      *
diff --git a/modules/saml/lib/IdP/SAML1.php b/modules/saml/lib/IdP/SAML1.php
index 3f1d734d4331da523cf0d5301d9737004cf8ad14..e158a666b590f08c8b7add6e289ce48a4cd2b324 100644
--- a/modules/saml/lib/IdP/SAML1.php
+++ b/modules/saml/lib/IdP/SAML1.php
@@ -3,6 +3,8 @@
 namespace SimpleSAML\Module\saml\IdP;
 
 use SimpleSAML\Bindings\Shib13\HTTPPost;
+use SimpleSAML\Utils\Config\Metadata;
+use SimpleSAML\Utils\Crypto;
 use SimpleSAML\Utils\HTTP;
 
 /**
@@ -13,6 +15,112 @@ use SimpleSAML\Utils\HTTP;
 
 class SAML1
 {
+
+    /**
+     * Retrieve the metadata of a hosted SAML 1.1 IdP.
+     *
+     * @param string $entityid The entity ID of the hosted SAML 1.1 IdP whose metadata we want.
+     *
+     * @return array
+     * @throws \SimpleSAML\Error\Exception
+     * @throws \SimpleSAML\Error\MetadataNotFound
+     * @throws \SimpleSAML_Error_Exception
+     */
+    public static function getHostedMetadata($entityid)
+    {
+        $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
+        $config = $handler->getMetaDataConfig($entityid, 'shib13-idp-hosted');
+
+        $metadata = [
+            'metadata-set' => 'shib13-idp-hosted',
+            'entityid' => $entityid,
+            'SignleSignOnService' => $handler->getGenerated('SingleSignOnService', 'shib13-idp-hosted'),
+            'NameIDFormat' => $config->getArrayizeString('NameIDFormat', 'urn:mace:shibboleth:1.0:nameIdentifier'),
+            'contacts' => [],
+        ];
+
+        // add certificates
+        $keys = [];
+        $certInfo = Crypto::loadPublicKey($config, false, 'new_');
+        $hasNewCert = false;
+        if ($certInfo !== null) {
+            $keys[] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => true,
+                'X509Certificate' => $certInfo['certData'],
+                'prefix' => 'new_',
+            ];
+            $hasNewCert = true;
+        }
+
+        $certInfo = Crypto::loadPublicKey($config, true);
+        $keys[] = [
+            'type' => 'X509Certificate',
+            'signing' => true,
+            'encryption' => $hasNewCert === false,
+            'X509Certificate' => $certInfo['certData'],
+            'prefix' => '',
+        ];
+        $metadata['keys'] = $keys;
+
+        // add organization information
+        if ($config->hasValue('OrganizationName')) {
+            $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName');
+            $metadata['OrganizationDisplayName'] = $config->getLocalizedString(
+                'OrganizationDisplayName',
+                $metadata['OrganizationName']
+            );
+
+            if (!$config->hasValue('OrganizationURL')) {
+                throw new \SimpleSAMl\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.');
+            }
+            $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL');
+        }
+
+        // add scope
+        if ($config->hasValue('scope')) {
+            $metadata['scope'] = $config->getArray('scope');
+        }
+
+        // add extensions
+        if ($config->hasValue('EntityAttributes')) {
+            $metadata['EntityAttributes'] = $config->getArray('EntityAttributes');
+
+            // check for entity categories
+            if (Metadata::isHiddenFromDiscovery($metadata)) {
+                $metadata['hide.from.discovery'] = true;
+            }
+        }
+
+        if ($config->hasValue('UIInfo')) {
+            $metadata['UIInfo'] = $config->getArray('UIInfo');
+        }
+
+        if ($config->hasValue('DiscoHints')) {
+            $metadata['DiscoHints'] = $config->getArray('DiscoHints');
+        }
+
+        if ($config->hasValue('RegistrationInfo')) {
+            $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo');
+        }
+
+        // add contact information
+        $globalConfig = \SimpleSAML\Configuration::getInstance();
+        $email = $globalConfig->getString('technicalcontact_email', false);
+        if ($email && $email !== 'na@example.org') {
+            $contact = [
+                'emailAddress' => $email,
+                'name' => $globalConfig->getString('technicalcontact_name', null),
+                'contactType' => 'technical',
+            ];
+            $metadata['contacts'][] = Metadata::getContact($contact);
+        }
+
+        return $metadata;
+    }
+
+
     /**
      * Send a response to the SP.
      *
diff --git a/modules/saml/lib/IdP/SAML2.php b/modules/saml/lib/IdP/SAML2.php
index f65cdaf4f93f69d44214c7d31e5473b8f588d944..f530451e0f861ec653a0a356e9f8de7827233e1b 100644
--- a/modules/saml/lib/IdP/SAML2.php
+++ b/modules/saml/lib/IdP/SAML2.php
@@ -3,9 +3,13 @@
 namespace SimpleSAML\Module\saml\IdP;
 
 use RobRichards\XMLSecLibs\XMLSecurityKey;
+use SAML2\Constants;
 use SimpleSAML\Configuration;
 use SimpleSAML\Logger;
 use SAML2\SOAP;
+use SimpleSAML\Utils\Config\Metadata;
+use SimpleSAML\Utils\Crypto;
+use SimpleSAML\Utils\HTTP;
 
 /**
  * IdP implementation for SAML 2.0 protocol.
@@ -697,6 +701,204 @@ class SAML2
     }
 
 
+    /**
+     * Retrieve the metadata of a hosted SAML 2 IdP.
+     *
+     * @param string $entityid The entity ID of the hosted SAML 2 IdP whose metadata we want.
+     *
+     * @return array
+     * @throws \SimpleSAML\Error\CriticalConfigurationError
+     * @throws \SimpleSAML\Error\Exception
+     * @throws \SimpleSAML\Error\MetadataNotFound
+     */
+    public static function getHostedMetadata($entityid)
+    {
+        $handler = \SimpleSAML\Metadata\MetaDataStorageHandler::getMetadataHandler();
+        $config = $handler->getMetaDataConfig($entityid, 'saml20-idp-hosted');
+
+        // configure endpoints
+        $ssob = $handler->getGenerated('SingleSignOnServiceBinding', 'saml20-idp-hosted');
+        $slob = $handler->getGenerated('SingleLogoutServiceBinding', 'saml20-idp-hosted');
+        $ssol = $handler->getGenerated('SingleSignOnService', 'saml20-idp-hosted');
+        $slol = $handler->getGenerated('SingleLogoutService', 'saml20-idp-hosted');
+
+        $sso = [];
+        if (is_array($ssob)) {
+            foreach ($ssob as $binding) {
+                $sso[] = [
+                    'Binding'  => $binding,
+                    'Location' => $ssol,
+                ];
+            }
+        } else {
+            $sso[] = [
+                'Binding'  => $ssob,
+                'Location' => $ssol,
+            ];
+        }
+
+        $slo = [];
+        if (is_array($slob)) {
+            foreach ($slob as $binding) {
+                $slo[] = [
+                    'Binding'  => $binding,
+                    'Location' => $slol,
+                ];
+            }
+        } else {
+            $slo[] = [
+                'Binding'  => $slob,
+                'Location' => $slol,
+            ];
+        }
+
+        $metadata = [
+            'metadata-set' => 'saml20-idp-hosted',
+            'entityid' => $entityid,
+            'SingleSignOnService' => $sso,
+            'SingleLogoutService' => $slo,
+            'NameIDFormat' => $config->getArrayizeString('NameIDFormat', Constants::NAMEID_TRANSIENT),
+        ];
+
+        // add certificates
+        $keys = [];
+        $certInfo = Crypto::loadPublicKey($config, false, 'new_');
+        $hasNewCert = false;
+        if ($certInfo !== null) {
+            $keys[] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => true,
+                'X509Certificate' => $certInfo['certData'],
+                'prefix' => 'new_',
+            ];
+            $hasNewCert = true;
+        }
+
+        $certInfo = Crypto::loadPublicKey($config, true);
+        $keys[] = [
+            'type' => 'X509Certificate',
+            'signing' => true,
+            'encryption' => $hasNewCert === false,
+            'X509Certificate' => $certInfo['certData'],
+            'prefix' => '',
+        ];
+
+        if ($config->hasValue('https.certificate')) {
+            $httpsCert = Crypto::loadPublicKey($config, true, 'https.');
+            $keys[] = [
+                'type' => 'X509Certificate',
+                'signing' => true,
+                'encryption' => false,
+                'X509Certificate' => $httpsCert['certData'],
+                'prefix' => 'https.'
+            ];
+        }
+        $metadata['keys'] = $keys;
+
+        // add ArtifactResolutionService endpoint, if enabled
+        if ($config->getBoolean('saml20.sendartifact', false)) {
+            $metadata['ArtifactResolutionService'][] = [
+                'index' => 0,
+                'Binding' => Constants::BINDING_SOAP,
+                'Location' => HTTP::getBaseURL().'saml2/idp/ArtifactResolutionService.php'
+            ];
+        }
+
+        // add Holder of Key, if enabled
+        if ($config->getBoolean('saml20.hok.assertion', false)) {
+            array_unshift(
+                $metadata['SingleSignOnService'],
+                [
+                    'hoksso:ProtocolBinding' => Constants::BINDING_HTTP_REDIRECT,
+                    'Binding' => Constants::BINDING_HOK_SSO,
+                    'Location' => HTTP::getBaseURL().'saml2/idp/SSOService.php',
+                ]
+            );
+        }
+
+        // add ECP profile, if enabled
+        if ($config->getBoolean('saml20.ecp', false)) {
+            $metadata['SingleSignOnService'][] = [
+                'index' => 0,
+                'Binding' => Constants::BINDING_SOAP,
+                'Location' => HTTP::getBaseURL().'saml2/idp/SSOService.php',
+            ];
+        }
+
+        // add organization information
+        if ($config->hasValue('OrganizationName')) {
+            $metadata['OrganizationName'] = $config->getLocalizedString('OrganizationName');
+            $metadata['OrganizationDisplayName'] = $config->getLocalizedString(
+                'OrganizationDisplayName',
+                $metadata['OrganizationName']
+            );
+
+            if (!$config->hasValue('OrganizationURL')) {
+                throw new \SimpleSAML\Error\Exception('If OrganizationName is set, OrganizationURL must also be set.');
+            }
+            $metadata['OrganizationURL'] = $config->getLocalizedString('OrganizationURL');
+        }
+
+        // add scope
+        if ($config->hasValue('scope')) {
+            $metadata['scope'] = $config->getArray('scope');
+        }
+
+        // add extensions
+        if ($config->hasValue('EntityAttributes')) {
+            $metadata['EntityAttributes'] = $config->getArray('EntityAttributes');
+
+            // check for entity categories
+            if (Metadata::isHiddenFromDiscovery($metadata)) {
+                $metadata['hide.from.discovery'] = true;
+            }
+        }
+
+        if ($config->hasValue('UIInfo')) {
+            $metadata['UIInfo'] = $config->getArray('UIInfo');
+        }
+
+        if ($config->hasValue('DiscoHints')) {
+            $metadata['DiscoHints'] = $config->getArray('DiscoHints');
+        }
+
+        if ($config->hasValue('RegistrationInfo')) {
+            $metadata['RegistrationInfo'] = $config->getArray('RegistrationInfo');
+        }
+
+        // configure signature options
+        if ($config->hasValue('validate.authnrequest')) {
+            $metadata['sign.authnrequest'] = $config->getBoolean('validate.authnrequest');
+        }
+
+        if ($config->hasValue('redirect.validate')) {
+            $metadata['redirect.sign'] = $config->getBoolean('redirect.validate');
+        }
+
+        // add contact information
+        if ($config->hasValue('contacts')) {
+            $contacts = $config->getArray('contacts');
+            foreach ($contacts as $contact) {
+                $metadata['contacts'][] = Metadata::getContact($contact);
+            }
+        }
+
+        $globalConfig = \SimpleSAML\Configuration::getInstance();
+        $email = $globalConfig->getString('technicalcontact_email', false);
+        if ($email && $email !== 'na@example.org') {
+            $contact = [
+                'emailAddress' => $email,
+                'name' => $globalConfig->getString('technicalcontact_name', null),
+                'contactType' => 'technical',
+            ];
+            $metadata['contacts'][] = Metadata::getContact($contact);
+        }
+
+        return $metadata;
+    }
+
+
     /**
      * Calculate the NameID value that should be used.
      *