diff --git a/config-templates/config.php b/config-templates/config.php
index 1f9a8653294bddac9a1507b09901b87e045e29d4..64c48d950c4e3e473bc72b5b3b346257f2ef5942 100644
--- a/config-templates/config.php
+++ b/config-templates/config.php
@@ -67,7 +67,8 @@ $config = array (
 	
 	/*
 	 * Some information about the technical persons running this installation.
-	 * The email address will be used as the recipient address for error reports.
+	 * The email address will be used as the recipient address for error reports, and
+	 * also as the technical contact in generated metadata.
 	 */
 	'technicalcontact_name'     => 'Administrator',
 	'technicalcontact_email'    => 'na@example.org',
diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index b66d06b48eb87b35ec70abcf020530487c2a1985..c963f5498e189e1169a6b4c2fc351010c4789e86 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -32,8 +32,8 @@ class SimpleSAML_Metadata_SAMLBuilder {
 
 		$this->document = new DOMDocument();
 		$this->entityDescriptor = $this->createElement('EntityDescriptor');
-
 		$this->entityDescriptor->setAttribute('entityID', $entityId);
+		$this->document->appendChild($this->entityDescriptor);
 	}
 
 
@@ -48,6 +48,18 @@ class SimpleSAML_Metadata_SAMLBuilder {
 	}
 
 
+	/**
+	 * Retrieve the EntityDescriptor as text.
+	 *
+	 * This function serializes this EntityDescriptor, and returns it as text.
+	 *
+	 * @return string  The serialized EntityDescriptor.
+	 */
+	public function getEntityDescriptorText() {
+		return $this->document->saveXML();
+	}
+
+
 	/**
 	 * Add metadata set for entity.
 	 *
@@ -112,6 +124,7 @@ class SimpleSAML_Metadata_SAMLBuilder {
 
 		if (array_key_exists('AssertionConsumerService', $metadata)) {
 			$t = $this->createElement('AssertionConsumerService');
+			$t->setAttribute('index', '0');
 			$t->setAttribute('Binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST');
 			$t->setAttribute('Location', $metadata['AssertionConsumerService']);
 			$e->appendChild($t);
@@ -184,6 +197,7 @@ class SimpleSAML_Metadata_SAMLBuilder {
 
 		if (array_key_exists('AssertionConsumerService', $metadata)) {
 			$t = $this->createElement('AssertionConsumerService');
+			$t->setAttribute('index', '0');
 			$t->setAttribute('Binding', 'urn:oasis:names:tc:SAML:1.0:profiles:browser-post');
 			$t->setAttribute('Location', $metadata['AssertionConsumerService']);
 			$e->appendChild($t);
@@ -223,6 +237,84 @@ class SimpleSAML_Metadata_SAMLBuilder {
 	}
 
 
+	/**
+	 * Add contact information.
+	 *
+	 * Accepts a contact type, and an array of the following elements (all are optional):
+	 * - emailAddress     Email address (as string), or array of email addresses.
+	 * - telephoneNumber  Telephone number of contact (as string), or array of telephone numbers.
+	 * - name             Full name of contact, either as <GivenName> <SurName>, or as <SurName>, <GivenName>.
+	 * - surName          Surname of contact.
+	 * - givenName        Givenname of contact.
+	 * - company          Company name of contact.
+	 *
+	 * 'name' will only be used if neither givenName nor surName is present.
+	 *
+	 * The following contact types are allowed:
+	 * "technical", "support", "administrative", "billing", "other"
+	 *
+	 * @param string $type  The type of contact.
+	 * @param array $details  The details about the contact.
+	 */
+	public function addContact($type, $details) {
+		assert('is_string($type)');
+		assert('is_array($details)');
+		assert('in_array($type, array("technical", "support", "administrative", "billing", "other"), TRUE)');
+
+		/* Parse name into givenName and surName. */
+		if (isset($details['name']) && empty($details['surName']) && empty($details['givenName'])) {
+			$names = explode(',', $details['name'], 2);
+			if (count($names) === 2) {
+				$details['surName'] = trim($names[0]);
+				$details['givenName'] = trim($names[1]);
+			} else {
+				$names = explode(' ', $details['name'], 2);
+				if (count($names) === 2) {
+					$details['givenName'] = trim($names[0]);
+					$details['surName'] = trim($names[1]);
+				} else {
+					$details['surName'] = trim($names[0]);
+				}
+			}
+		}
+
+		$e = $this->createElement('ContactPerson');
+		$e->setAttribute('contactType', $type);
+
+		if (isset($details['company'])) {
+			$e->appendChild($this->createTextElement('Company', $details['company']));
+		}
+		if (isset($details['givenName'])) {
+			$e->appendChild($this->createTextElement('GivenName', $details['givenName']));
+		}
+		if (isset($details['surName'])) {
+			$e->appendChild($this->createTextElement('SurName', $details['surName']));
+		}
+
+		if (isset($details['emailAddress'])) {
+			$eas = $details['emailAddress'];
+			if (!is_array($eas)) {
+				$eas = array($eas);
+			}
+			foreach ($eas as $ea) {
+				$e->appendChild($this->createTextElement('EmailAddress', $ea));
+			}
+		}
+
+		if (isset($details['telephoneNumber'])) {
+			$tlfNrs = $details['telephoneNumber'];
+			if (!is_array($tlfNrs)) {
+				$tlfNrs = array($tlfNrs);
+			}
+			foreach ($tlfNrs as $tlfNr) {
+				$e->appendChild($this->createTextElement('TelephoneNumber', $tlfNr));
+			}
+		}
+
+		$this->entityDescriptor->appendChild($e);
+	}
+
+
 	/**
 	 * Create DOMElement in metadata namespace.
 	 *
@@ -238,6 +330,24 @@ class SimpleSAML_Metadata_SAMLBuilder {
 	}
 
 
+	/**
+	 * Create a DOMElement in metadata namespace with a single text node.
+	 *
+	 * @param string $name  The name of the DOMElement.
+	 * @param string $text  The text contained in the element.
+	 * @return DOMElement  The new DOMElement with a text node.
+	 */
+	private function createTextElement($name, $text) {
+		assert('is_string($name)');
+		assert('is_string($text)');
+
+		$node = $this->createElement($name);
+		$node->appendChild($this->document->createTextNode($text));
+
+		return $node;
+	}
+
+
 	/**
 	 * Add certificate.
 	 *
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index 6c7fb1d44fe6ba70ed1944b8dac4695453b27d2b..f4fee907348cba3a12d8272c02192bf0c3981f98 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -38,57 +38,30 @@ try {
 	
 	$urlSLO = $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted', array('logouttype' => $logouttype));
 	$urlSLOr = $metadata->getGenerated('SingleLogoutServiceResponse', 'saml20-idp-hosted', array('logouttype' => $logouttype));
-	
-	$metaflat = "
-	'" . htmlspecialchars($idpentityid) . "' =>  array(
-		'name'                 => 'Type in a name for this entity',
-		'description'          => 'and a proper description that would help users know when to select this IdP.',
-		'SingleSignOnService'  => '" . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted', array())) . "',
-		'SingleLogoutService'  => '" . htmlspecialchars($urlSLO) . "'," . 
-			(($urlSLO !== $urlSLOr) ? "
-		'SingleLogoutServiceResponse'  => '" . htmlspecialchars($urlSLOr) . "'," : "") . "
-		'certFingerprint'      => '" . strtolower(sha1(base64_decode($data))) ."'
-	),
-";
 
+	$metaArray = array(
+		'name' => 'Type in a name for this entity',
+		'description' => 'and a proper description that would help users know when to select this IdP.',
+		'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted', array()),
+		'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-idp-hosted', array('logouttype' => $logouttype)),
+		'SingleLogoutServiceResponse'  => $metadata->getGenerated('SingleLogoutServiceResponse', 'saml20-idp-hosted', array('logouttype' => $logouttype)),
+		'certFingerprint' => strtolower(sha1(base64_decode($data))),
+	);
+
+	if ($metaArray['SingleLogoutServiceResponse'] === $metaArray['SingleLogoutService']) {
+		unset($metaArray['SingleLogoutServiceResponse']);
+	}
+
+	$metaflat = var_export($idpentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ',';
 
-	
-	$metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-	<EntityDescriptor xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
- entityID="' . htmlspecialchars($idpentityid) . '">
-    <IDPSSODescriptor
-        WantAuthnRequestsSigned="false"
-        protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
-        
-		<KeyDescriptor use="signing">
-			<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
-				<ds:X509Data>
-					<ds:X509Certificate>' . htmlspecialchars($data) . '</ds:X509Certificate>
-				</ds:X509Data>
-			</ds:KeyInfo>
-		</KeyDescriptor>  
-        
-
-        
-        <!-- Logout endpoints -->
-        <SingleLogoutService
-            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
-            Location="' . htmlspecialchars($urlSLO) . '"
-            ResponseLocation="' . htmlspecialchars($urlSLOr) . '"
-            />
-
-        
-        <!-- Supported Name Identifier Formats -->
-        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
-        
-        <!-- AuthenticationRequest Consumer endpoint -->
-        <SingleSignOnService
-            Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
-            Location="' . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'saml20-idp-hosted')) . '"
-            />
-        
-    </IDPSSODescriptor>
-</EntityDescriptor>';
+	$metaArray['certificate'] = $idpmeta['certificate'];
+	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);
+	$metaBuilder->addMetadataIdP20($metaArray);
+	$metaBuilder->addContact('technical', array(
+		'emailAddress' => $config->getValue('technicalcontact_email'),
+		'name' => $config->getValue('technicalcontact_name'),
+		));
+	$metaxml = $metaBuilder->getEntityDescriptorText();
 
 	/* Sign the metadata if enabled. */
 	$metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'SAML 2 IdP');
diff --git a/www/saml2/sp/metadata.php b/www/saml2/sp/metadata.php
index 9499fd5897bfa56dba5d502e4039ff897eb62e30..0287dc3e3fe1a4b365770aec4bf8cfc9d5f5ca66 100644
--- a/www/saml2/sp/metadata.php
+++ b/www/saml2/sp/metadata.php
@@ -46,37 +46,24 @@ try {
 	if (!$spmeta['assertionConsumerServiceURL']) throw new Exception('The following parameter is not set in your SAML 2.0 SP Hosted metadata: assertionConsumerServiceURL');
 	if (!$spmeta['SingleLogOutUrl']) throw new Exception('The following parameter is not set in your SAML 2.0 SP Hosted metadata: SingleLogOutUrl');
 	*/
-	
-	$metaflat = "
-	'" . htmlspecialchars($spentityid) . "' => array(
- 		'AssertionConsumerService' => '" . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . "',
- 		'SingleLogoutService'      => '" . htmlspecialchars($metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted')) . "'
-	),
-";
-	
-	$metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<EntityDescriptor entityID="' . htmlspecialchars($spentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
 
-	<SPSSODescriptor 
-		AuthnRequestsSigned="false" 
-		WantAssertionsSigned="false" 
-		protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+	$metaArray = array(
+		'AssertionConsumerService' => $metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted'),
+		'SingleLogoutService' => $metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted'),
+	);
 
-		<SingleLogoutService 
-			Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" 
-			Location="' . htmlspecialchars($metadata->getGenerated('SingleLogoutService', 'saml20-sp-hosted')) . '"/>
-		
-		<NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
-		
-		<AssertionConsumerService 
-			index="0" 
-			isDefault="true" 
-			Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" 
-			Location="' . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . '" />
-
-	</SPSSODescriptor>
+	$metaflat = var_export($spentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ',';
 
-</EntityDescriptor>';
+	if (array_key_exists('certificate', $spmeta)) {
+		$metaArray['certificate'] = $spmeta['certificate'];
+	}
+	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($spentityid);
+	$metaBuilder->addMetadataSP20($metaArray);
+	$metaBuilder->addContact('technical', array(
+		'emailAddress' => $config->getValue('technicalcontact_email'),
+		'name' => $config->getValue('technicalcontact_name'),
+		));
+	$metaxml = $metaBuilder->getEntityDescriptorText();
 
 	/* Sign the metadata if enabled. */
 	$metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'SAML 2 SP');
diff --git a/www/shib13/idp/metadata.php b/www/shib13/idp/metadata.php
index 526c74581e2da8ca0d7b34c6296a9910f93b3ece..d5ecbfcf5b25b412c8ebf3c50ef6e4aba67d2af2 100644
--- a/www/shib13/idp/metadata.php
+++ b/www/shib13/idp/metadata.php
@@ -34,41 +34,23 @@ try {
 	$data = XMLSecurityDSig::get509XCert($cert, true);
 	
 	
-	$metaflat = "
-	'" . htmlspecialchars($idpentityid) . "' =>  array(
-		'name'                 => 'Type in a name for this entity',
-		'description'          => 'and a proper description that would help users know when to select this IdP.',
-		'SingleSignOnService'  => '" . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted')) . "',
-		'certFingerprint'      => '" . strtolower(sha1(base64_decode($data))) ."'
-	),
-";
+	$metaArray = array(
+		'name' => 'Type in a name for this entity',
+		'description' => 'and a proper description that would help users know when to select this IdP.',
+		'SingleSignOnService' => $metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted'),
+		'certFingerprint' => strtolower(sha1(base64_decode($data))),
+	);
+
+	$metaflat = var_export($idpentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ',';
 	
-	$metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<EntityDescriptor entityID="' . htmlspecialchars($idpentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
-
-	<IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0">
-
-		<KeyDescriptor use="signing">
-			<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
-				<ds:X509Data>
-					<ds:X509Certificate>' . htmlspecialchars($data) . '</ds:X509Certificate>
-				</ds:X509Data>
-			</ds:KeyInfo>
-		</KeyDescriptor>
-
-		<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
-		
-		<SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
-			Location="' . htmlspecialchars($metadata->getGenerated('SingleSignOnService', 'shib13-idp-hosted')) . '"/>
-
-	</IDPSSODescriptor>
-
-	<ContactPerson contactType="technical">
-		<SurName>' . $config->getValue('technicalcontact_name', 'Not entered') . '</SurName>
-		<EmailAddress>' . $config->getValue('technicalcontact_email', 'Not entered') . '</EmailAddress>
-	</ContactPerson>
-	
-</EntityDescriptor>';
+	$metaArray['certificate'] = $idpmeta['certificate'];
+	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);
+	$metaBuilder->addMetadataIdP11($metaArray);
+	$metaBuilder->addContact('technical', array(
+		'emailAddress' => $config->getValue('technicalcontact_email'),
+		'name' => $config->getValue('technicalcontact_name'),
+		));
+	$metaxml = $metaBuilder->getEntityDescriptorText();
 
 	/* Sign the metadata if enabled. */
 	$metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $idpmeta, 'Shib 1.3 IdP');
diff --git a/www/shib13/sp/metadata.php b/www/shib13/sp/metadata.php
index b6c08f6bff6844797a4bb51d549aa96354ecc34a..23dc81001fd625f70f7089e139a0caded1d782ed 100644
--- a/www/shib13/sp/metadata.php
+++ b/www/shib13/sp/metadata.php
@@ -27,28 +27,22 @@ try {
 	$spentityid = isset($_GET['spentityid']) ? $_GET['spentityid'] : $metadata->getMetaDataCurrentEntityID('shib13-sp-hosted');
 	
 
-	$metaflat = "
-	'" . htmlspecialchars($spentityid) . "' => array(
- 		'AssertionConsumerService' => '" . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'saml20-sp-hosted')) . "'
-	),
-";
-	
-	$metaxml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<EntityDescriptor entityID="' . htmlspecialchars($spentityid) . '" xmlns="urn:oasis:names:tc:SAML:2.0:metadata">
-	<SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol">
+	$metaArray = array(
+		'AssertionConsumerService' => $metadata->getGenerated('AssertionConsumerService', 'shib13-sp-hosted'),
+	);
 
-		<NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
-		
-		<AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="' . htmlspecialchars($metadata->getGenerated('AssertionConsumerService', 'shib13-sp-hosted')) . '" index="1" isDefault="true" />
-		
-	</SPSSODescriptor>
-	
-	<ContactPerson contactType="technical">
-		<SurName>' . $config->getValue('technicalcontact_name', 'Not entered') . '</SurName>
-		<EmailAddress>' . $config->getValue('technicalcontact_email', 'Not entered') . '</EmailAddress>
-	</ContactPerson>
-		
-</EntityDescriptor>';
+	$metaflat = var_export($spentityid, TRUE) . ' => ' . var_export($metaArray, TRUE) . ',';
+
+	if (array_key_exists('certificate', $spmeta)) {
+		$metaArray['certificate'] = $spmeta['certificate'];
+	}
+	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($spentityid);
+	$metaBuilder->addMetadataSP11($metaArray);
+	$metaBuilder->addContact('technical', array(
+		'emailAddress' => $config->getValue('technicalcontact_email'),
+		'name' => $config->getValue('technicalcontact_name'),
+		));
+	$metaxml = $metaBuilder->getEntityDescriptorText();
 
 	/* Sign the metadata if enabled. */
 	$metaxml = SimpleSAML_Metadata_Signer::sign($metaxml, $spmeta, 'Shib 1.3 SP');