diff --git a/docs/simplesamlphp-metadata-extensions-attributes.txt b/docs/simplesamlphp-metadata-extensions-attributes.txt
new file mode 100644
index 0000000000000000000000000000000000000000..fb9dad61a7587546420887dea9045b25b810feac
--- /dev/null
+++ b/docs/simplesamlphp-metadata-extensions-attributes.txt
@@ -0,0 +1,113 @@
+SAML V2.0 Metadata Extensions for Login and Discovery User Interface
+=============================
+
+<!--
+	This file is written in Markdown syntax.
+	For more information about how to use the Markdown syntax, read here:
+	http://daringfireball.net/projects/markdown/syntax
+-->
+
+  * Version: `$Id:$`
+  * Author: Timothy Ace [tace@synacor.com](mailto:tace@synacor.com)
+
+<!-- {{TOC}} -->
+
+This is a reference for the SimpleSAMLphp implemenation of the [SAML
+V2.0 Attribute Extensions](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-attribute-ext.pdf)
+defined by OASIS.
+
+The `metadata/saml20-idp-hosted.php` entries are used to define the
+metadata extension items. An example of this is:
+
+    <?php
+    $metadata['entity-id-1'] = array(
+        /* ... */
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+			'{urn:simplesamlphp:v1}foo'          => array('bar'),
+		),
+        /* ... */
+    );
+
+The OASIS specification primarily defines how to include arbitrary
+`Attribute` and `Assertion` elements within the metadata for an IdP.
+
+*Note*: SimpleSAMLphp does not support `Assertion` elements within the
+metadata at this time.
+
+Defining Attributes
+--------------
+
+The `EntityAttributes` key is used to define the attributes in the
+metadata. Each item in the `EntityAttributes` array defines a new
+`<Attribute>` item in the metadata. The value for each key must be an
+array. Each item in this array produces a separte `<AttributeValue>`
+element within the `<Attribute>` element.
+
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+		),
+
+This generates:
+
+      <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:simplesamlphp:v1:simplesamlphp" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">is</saml:AttributeValue>
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">really</saml:AttributeValue>
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">cool</saml:AttributeValue>
+      </saml:Attribute>
+
+Each `<Attribute>` element requires a `NameFormat` attribute. This is
+specified using curly braces at the beginning of the key name:
+
+		'EntityAttributes' => array(
+			'{urn:simplesamlphp:v1}foo' => array('bar'),
+		),
+
+This generates:
+
+      <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="foo" NameFormat="urn:simplesamlphp:v1">
+        <saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">bar</saml:AttributeValue>
+      </saml:Attribute>
+
+When the curly braces are omitted, the NameFormat is automatically set
+to "urn:oasis:names:tc:SAML:2.0:attrname-format:uri".
+
+Generated XML Metadata Examples
+----------------
+
+If given the following configuration...
+
+    $metadata['https://www.example.com/saml/saml2/idp/metadata.php'] = array(
+        'host' => 'www.example.com',
+        'certificate' => 'server.crt',
+        'privatekey' => 'server.pem',
+        'auth' => 'example-userpass',
+
+		'EntityAttributes' => array(
+			'urn:simplesamlphp:v1:simplesamlphp' => array('is', 'really', 'cool'),
+			'{urn:simplesamlphp:v1}foo'          => array('bar'),
+		),
+	);
+
+... will generate the following XML metadata:
+
+	<?xml version="1.0"?>
+	<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://www.example.com/saml/saml2/idp/metadata.php">
+	  <md:Extensions>
+		<mdattr:EntityAttributes xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute">
+		  <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="urn:simplesamlphp:v1:simplesamlphp" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">is</saml:AttributeValue>
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">really</saml:AttributeValue>
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">cool</saml:AttributeValue>
+		  </saml:Attribute>
+		  <saml:Attribute xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" Name="foo" NameFormat="urn:simplesamlphp:v1">
+			<saml:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" xsi:type="xs:string">bar</saml:AttributeValue>
+		  </saml:Attribute>
+		</mdattr:EntityAttributes>
+	  </md:Extensions>
+	  <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+		<md:KeyDescriptor use="signing">
+		  <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+			<ds:X509Data>
+            ...
+
diff --git a/docs/simplesamlphp-metadata-extensions-ui.txt b/docs/simplesamlphp-metadata-extensions-ui.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7585a32cd66e59a8e7bb0ba84d3bfd2f7920e2e4
--- /dev/null
+++ b/docs/simplesamlphp-metadata-extensions-ui.txt
@@ -0,0 +1,265 @@
+SAML V2.0 Metadata Extensions for Login and Discovery User Interface
+=============================
+
+<!--
+    This file is written in Markdown syntax.
+    For more information about how to use the Markdown syntax, read here:
+    http://daringfireball.net/projects/markdown/syntax
+-->
+
+  * Version: `$Id:$`
+  * Author: Timothy Ace [tace@synacor.com](mailto:tace@synacor.com)
+
+<!-- {{TOC}} -->
+
+This is a reference for the SimpleSAMLphp implemenation of the [SAML
+V2.0 Metadata Extensions for Login and Discovery User Interface](http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf)
+defined by OASIS.
+
+The `metadata/saml20-idp-hosted.php` entries are used to define the
+metadata extension items. An example of this is:
+
+    <?php
+    $metadata['entity-id-1'] = array(
+        /* ... */
+        'UIInfo' => array(
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicaciĂłn', 'sesiĂłn federated'),
+            ),
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                    'lang'   => 'en',
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+        ),
+        'DiscoHints' => array(
+            'IPHint'          => array('130.59.0.0/16', '2001:620::0/96'),
+            'DomainHint'      => array('example.com', 'www.example.com'),
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+        ),
+        /* ... */
+    );
+
+The OASIS specification primarily defines how an IdP can communicate
+metadata related to IdP discovery. There are two different types of
+extensions defined. There are the `<mdui:UIInfo>`elements that define
+how an IdP should be displayed and there are the `<mdui:DiscoHints>`
+elements that define when an IdP should be choosen/displayed.
+
+UIInfo Items
+--------------
+
+These elements are used for IdP discovery to determine what to display
+about an IdP. These properties are all children of the `UIInfo` key.
+
+*Note*: Most elements are localized strings that specify the language
+using the array key as the language-code:
+
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+
+`DisplayName`
+:   The localized list of names for this IdP
+
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+
+`Description`
+:   The localized list of statements used to decribe this IdP
+
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+
+`InformationURL`
+:   A localized list of URLs where more information about the IdP is
+    located.
+
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+
+`PrivacyStatementURL`
+:   A localized list of URLs where the IdP's privacy statement is
+    located.
+
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+
+`Keywords`
+:   A localized list of keywords used to describe the IdP
+
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicaciĂłn', 'sesiĂłn federated'),
+            ),
+
+:   *Note*: The `+` (plus) character is forbidden by specification from
+    being part of a Keyword.
+
+`Logo`
+:   The logos used to represent the IdP
+
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                    'lang'   => 'en',
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+
+:   An optional `lang` key containing a language-code is supported for
+    localized Logos.
+
+DiscoHints Items
+--------------
+
+These elements are used for IdP discovery to determine when to choose or
+present an IdP. These properties are all children of the `DiscoHints`
+key.
+
+`IPHint`
+:   This is a list of both IPv4 and IPv6 addresses in CIDR notation
+    services by or associated with this entity.
+
+            'IPHint' => array('130.59.0.0/16', '2001:620::0/96'),
+
+`DomainHint`
+:   This specifies a list of domain names serviced by or associated with
+    this entity.
+
+            'DomainHint' => array('example.com', 'www.example.com'),
+
+`GeolocationHint`
+:   This specifies a list of geographic coordinates associated with, or
+    serviced by, the entity. Coordinates are given in URI form using the
+    geo URI scheme [RFC5870](http://www.ietf.org/rfc/rfc5870.txt).
+
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+
+
+Generated XML Metadata Examples
+----------------
+
+If given the following configuration...
+
+    $metadata['https://www.example.com/saml/saml2/idp/metadata.php'] = array(
+        'host' => 'www.example.com',
+        'certificate' => 'server.crt',
+        'privatekey' => 'server.pem',
+        'auth' => 'example-userpass',
+
+        'UIInfo' => array(
+            'DisplayName' => array(
+                'en' => 'English name',
+                'es' => 'Nombre en Español',
+            ),
+            'Description' => array(
+                'en' => 'English description',
+                'es' => 'Descripción en Español',
+            ),
+            'InformationURL' => array(
+                'en' => 'http://example.com/info/en',
+                'es' => 'http://example.com/info/es',
+            ),
+            'PrivacyStatementURL' => array(
+                'en' => 'http://example.com/privacy/en',
+                'es' => 'http://example.com/privacy/es',
+            ),
+            'Keywords' => array(
+                'en' => array('communication', 'federated session'),
+                'es' => array('comunicaciĂłn', 'sesiĂłn federated'),
+            ),
+            'Logo' => array(
+                array(
+                    'url'    => 'http://example.com/logo1.png',
+                    'height' => 200,
+                    'width'  => 400,
+                ),
+                array(
+                    'url'    => 'http://example.com/logo2.png',
+                    'height' => 201,
+                    'width'  => 401,
+                ),
+            ),
+        ),
+        'DiscoHints' => array(
+            'IPHint'          => array('130.59.0.0/16', '2001:620::0/96'),
+            'DomainHint'      => array('example.com', 'www.example.com'),
+            'GeolocationHint' => array('geo:47.37328,8.531126', 'geo:19.34343,12.342514'),
+        ),
+    );
+
+... will generate the following XML metadata:
+
+    <?xml version="1.0"?>
+    <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="https://www.example.com/saml/saml2/idp/metadata.php">
+      <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <md:Extensions>
+          <mdui:UIInfo xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
+            <mdui:DisplayName xml:lang="en">English name</mdui:DisplayName>
+            <mdui:DisplayName xml:lang="es">Nombre en Espa&#xF1;ol</mdui:DisplayName>
+            <mdui:Description xml:lang="en">English description</mdui:Description>
+            <mdui:Description xml:lang="es">Descripci&#xF3;n en Espa&#xF1;ol</mdui:Description>
+            <mdui:InformationURL xml:lang="en">http://example.com/info/en</mdui:InformationURL>
+            <mdui:InformationURL xml:lang="es">http://example.com/info/es</mdui:InformationURL>
+            <mdui:PrivacyStatementURL xml:lang="en">http://example.com/privacy/en</mdui:PrivacyStatementURL>
+            <mdui:PrivacyStatementURL xml:lang="es">http://example.com/privacy/es</mdui:PrivacyStatementURL>
+            <mdui:Keywords xml:lang="en">communication federated+session</mdui:Keywords>
+            <mdui:Keywords xml:lang="es">comunicaci&#xF3;n sesi&#xF3;n+federated</mdui:Keywords>
+            <mdui:Logo width="400" height="200" xml:lang="en">http://example.com/logo1.png</mdui:Logo>
+            <mdui:Logo width="401" height="201">http://example.com/logo2.png</mdui:Logo>
+          </mdui:UIInfo>
+          <mdui:DiscoHints xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui">
+            <mdui:IPHint>130.59.0.0/16</mdui:IPHint>
+            <mdui:IPHint>2001:620::0/96</mdui:IPHint>
+            <mdui:DomainHint>example.com</mdui:DomainHint>
+            <mdui:DomainHint>www.example.com</mdui:DomainHint>
+            <mdui:GeolocationHint>geo:47.37328,8.531126</mdui:GeolocationHint>
+            <mdui:GeolocationHint>geo:19.34343,12.342514</mdui:GeolocationHint>
+          </mdui:DiscoHints>
+        </md:Extensions>
+        <md:KeyDescriptor use="signing">
+          <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
+            <ds:X509Data>
+            ...
+
diff --git a/lib/SAML2/XML/md/Extensions.php b/lib/SAML2/XML/md/Extensions.php
index 70d7d1cfdada248f4af09c1c473457ceb1d18c5e..d3d237acc2a25e10d8569aae5148a64d0f833a3c 100644
--- a/lib/SAML2/XML/md/Extensions.php
+++ b/lib/SAML2/XML/md/Extensions.php
@@ -24,6 +24,10 @@ class SAML2_XML_md_Extensions {
 				$ret[] = new SAML2_XML_mdattr_EntityAttributes($node);
 			} elseif ($node->namespaceURI === SAML2_XML_mdrpi_Common::NS_MDRPI && $node->localName === 'PublicationInfo') {
 				$ret[] = new SAML2_XML_mdrpi_PublicationInfo($node);
+			} elseif ($node->namespaceURI === SAML2_XML_mdui_UIInfo::NS && $node->localName === 'UIInfo') {
+				$ret[] = new SAML2_XML_mdui_UIInfo($node);
+			} elseif ($node->namespaceURI === SAML2_XML_mdui_DiscoHints::NS && $node->localName === 'DiscoHints') {
+				$ret[] = new SAML2_XML_mdui_DiscoHints($node);
 			} else {
 				$ret[] = new SAML2_XML_Chunk($node);
 			}
diff --git a/lib/SAML2/XML/mdui/DiscoHints.php b/lib/SAML2/XML/mdui/DiscoHints.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ff510d96fb50a98f6994698248a9ee54bbea46e
--- /dev/null
+++ b/lib/SAML2/XML/mdui/DiscoHints.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * Class for handling the metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_DiscoHints {
+
+	/**
+	 * The namespace used for the DiscoHints extension.
+	 */
+	const NS = 'urn:oasis:names:tc:SAML:metadata:ui';
+
+
+	/**
+	 * Array with child elements.
+	 *
+	 * The elements can be any of the other SAML2_XML_mdui_* elements.
+	 *
+	 * @var array
+	 */
+	public $children = array();
+
+
+	/**
+	 * The IPHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $IPHint = array();
+
+
+	/**
+	 * The DomainHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $DomainHint = array();
+
+
+	/**
+	 * The GeolocationHint, as an array of strings.
+	 *
+	 * @var array
+	 */
+	public $GeolocationHint = array();
+
+	/**
+	 * Create a DiscoHints element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->IPHint =          SAML2_Utils::extractStrings($xml, self::NS, 'IPHint');
+		$this->DomainHint =      SAML2_Utils::extractStrings($xml, self::NS, 'DomainHint');
+		$this->GeolocationHint = SAML2_Utils::extractStrings($xml, self::NS, 'GeolocationHint');
+
+		foreach (SAML2_Utils::xpQuery($xml, "./*[namespace-uri()!='".self::NS."']") as $node) {
+			$this->children[] = new SAML2_XML_Chunk($node);
+		}
+	}
+
+
+	/**
+	 * Convert this DiscoHints to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->IPHint)');
+		assert('is_array($this->DomainHint)');
+		assert('is_array($this->GeolocationHint)');
+		assert('is_array($this->children)');
+
+		if (!empty($this->IPHint)
+		 || !empty($this->DomainHint)
+		 || !empty($this->GeolocationHint)
+		 || !empty($this->children)) {
+			$doc = $parent->ownerDocument;
+
+			$e = $doc->createElementNS(self::NS, 'mdui:DiscoHints');
+			$parent->appendChild($e);
+
+			if (!empty($this->children)) {
+				foreach ($this->children as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:IPHint',          FALSE, $this->IPHint);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:DomainHint',      FALSE, $this->DomainHint);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:GeolocationHint', FALSE, $this->GeolocationHint);
+
+			return $e;
+		}
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/Keywords.php b/lib/SAML2/XML/mdui/Keywords.php
new file mode 100644
index 0000000000000000000000000000000000000000..0cc2893ea8629fa0dc0601570a25d06c7cfecbba
--- /dev/null
+++ b/lib/SAML2/XML/mdui/Keywords.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Class for handling the Keywords metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_Keywords {
+
+	/**
+	 * The keywords of this item.
+	 *
+	 * @var string
+	 */
+	public $Keywords;
+
+
+	/**
+	 * The language of this item.
+	 *
+	 * @var string
+	 */
+	public $lang;
+
+
+	/**
+	 * Initialize a Keywords.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('xml:lang')) {
+			throw new Exception('Missing lang on Keywords.');
+		}
+		if (!is_string($xml->textContent) || !strlen($xml->textContent)) {
+			throw new Exception('Missing value for Keywords.');
+		}
+		$this->Keywords = array();
+		foreach (explode(' ', $xml->textContent) as $keyword) {
+			$this->Keywords[] = str_replace('+', ' ', $keyword);
+		}
+		$this->lang = $xml->getAttribute('xml:lang');
+	}
+
+
+	/**
+	 * Convert this Keywords to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Keywords to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->lang)');
+		assert('is_array($this->Keywords)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_mdui_UIInfo::NS, 'mdui:Keywords');
+		$e->setAttribute('xml:lang', $this->lang);
+		$e->nodeValue = '';
+		foreach ($this->Keywords as $keyword) {
+			if (strpos($keyword, "+") !== false) {
+				throw new Exception('Keywords may not contain a "+" character.');
+			}
+			$e->nodeValue .= str_replace(' ', '+', $keyword) . ' ';
+		}
+		$e->nodeValue = rtrim($e->nodeValue);
+		$parent->appendChild($e);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/Logo.php b/lib/SAML2/XML/mdui/Logo.php
new file mode 100644
index 0000000000000000000000000000000000000000..1e327a1db0995f9b58c8bb615a61d24a3fb09eb7
--- /dev/null
+++ b/lib/SAML2/XML/mdui/Logo.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Class for handling the Logo metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_Logo {
+
+	/**
+	 * The url of this logo.
+	 *
+	 * @var string
+	 */
+	public $url;
+
+
+	/**
+	 * The width of this logo.
+	 *
+	 * @var string
+	 */
+	public $width;
+
+
+	/**
+	 * The height of this logo.
+	 *
+	 * @var string
+	 */
+	public $height;
+
+	/**
+	 * The language of this item.
+	 *
+	 * @var string
+	 */
+	public $lang;
+
+
+	/**
+	 * Initialize a Logo.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('width')) {
+			throw new Exception('Missing width of Logo.');
+		}
+		if (!$xml->hasAttribute('height')) {
+			throw new Exception('Missing height of Logo.');
+		}
+		if (!is_string($xml->textContent) || !strlen($xml->textContent)) {
+			throw new Exception('Missing url value for Logo.');
+		}
+		$this->url = $xml->textContent;
+		$this->width = (int)$xml->getAttribute('width');
+		$this->height = (int)$xml->getAttribute('height');
+		$this->lang = $xml->hasAttribute('xml:lang') ? $xml->getAttribute('xml:lang') : NULL;
+	}
+
+
+	/**
+	 * Convert this Logo to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Logo to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_int($this->width)');
+		assert('is_int($this->height)');
+		assert('is_string($this->url)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_mdui_UIInfo::NS, 'mdui:Logo');
+		$e->nodeValue = $this->url;
+		$e->setAttribute('width', (int)$this->width);
+		$e->setAttribute('height', (int)$this->height);
+		if (isset($this->lang)) {
+			$e->setAttribute('xml:lang', $this->lang);
+		}
+		$parent->appendChild($e);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/mdui/UIInfo.php b/lib/SAML2/XML/mdui/UIInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..2023abe73884aa2d68b20858c65531cff5e0b60f
--- /dev/null
+++ b/lib/SAML2/XML/mdui/UIInfo.php
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * Class for handling the metadata extensions for login and discovery user interface
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-saml-metadata-ui/v1.0/sstc-saml-metadata-ui-v1.0.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdui_UIInfo {
+
+	/**
+	 * The namespace used for the UIInfo extension.
+	 */
+	const NS = 'urn:oasis:names:tc:SAML:metadata:ui';
+
+
+	/**
+	 * Array with child elements.
+	 *
+	 * The elements can be any of the other SAML2_XML_mdui_* elements.
+	 *
+	 * @var array
+	 */
+	public $children = array();
+
+	/**
+	 * The DisplayName, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $DisplayName = array();
+
+	/**
+	 * The Description, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $Description = array();
+
+	/**
+	 * The InformationURL, as an array of language => url.
+	 *
+	 * @var array
+	 */
+	public $InformationURL = array();
+
+	/**
+	 * The PrivacyStatementURL, as an array of language => url.
+	 *
+	 * @var array
+	 */
+	public $PrivacyStatementURL = array();
+
+	/**
+	 * The Keywords, as an array of language => array of strings.
+	 *
+	 * @var array
+	 */
+	public $Keywords = array();
+
+	/**
+	 * The Logo, as an array of associative arrays containing url, width, height, and optional lang.
+	 *
+	 * @var array
+	 */
+	public $Logo = array();
+
+
+	/**
+	 * Create a UIInfo element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->DisplayName         = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'DisplayName');
+		$this->Description         = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'Description');
+		$this->InformationURL      = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'InformationURL');
+		$this->PrivacyStatementURL = SAML2_Utils::extractLocalizedStrings($xml, self::NS, 'PrivacyStatementURL');
+
+		foreach (SAML2_Utils::xpQuery($xml, './*') as $node) {
+			if ($node->namespaceURI === self::NS) {
+				switch ($node->localName) {
+					case 'Keywords':
+						$this->Keywords[] = new SAML2_XML_mdui_Keywords($node);
+					break;
+					case 'Logo':
+						$this->Logo[] = new SAML2_XML_mdui_Logo($node);
+					break;
+				}
+			} else {
+				$this->children[] = new SAML2_XML_Chunk($node);
+			}
+		}
+	}
+
+
+	/**
+	 * Convert this UIInfo to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->DisplayName)');
+		assert('is_array($this->InformationURL)');
+		assert('is_array($this->PrivacyStatementURL)');
+		assert('is_array($this->Keywords)');
+		assert('is_array($this->Logo)');
+		assert('is_array($this->children)');
+
+		if (!empty($this->DisplayName)
+		 || !empty($this->Description)
+		 || !empty($this->InformationURL)
+		 || !empty($this->PrivacyStatementURL)
+		 || !empty($this->Keywords)
+		 || !empty($this->Logo)
+		 || !empty($this->children)) {
+			$doc = $parent->ownerDocument;
+
+			$e = $doc->createElementNS(self::NS, 'mdui:UIInfo');
+			$parent->appendChild($e);
+
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:DisplayName',         TRUE, $this->DisplayName);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:Description',         TRUE, $this->Description);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:InformationURL',      TRUE, $this->InformationURL);
+			SAML2_Utils::addStrings($e, self::NS, 'mdui:PrivacyStatementURL', TRUE, $this->PrivacyStatementURL);
+
+			if (!empty($this->Keywords)) {
+				foreach ($this->Keywords as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			if (!empty($this->Logo)) {
+				foreach ($this->Logo as $child) {
+					$child->toXML($e);
+				}
+			}
+
+			if (!empty($this->children)) {
+				foreach ($this->children as $child) {
+					$child->toXML($e);
+				}
+			}
+		}
+		return $e;
+	}
+
+}
diff --git a/lib/SimpleSAML/Metadata/SAMLBuilder.php b/lib/SimpleSAML/Metadata/SAMLBuilder.php
index 3373d0f64987df7419d88c73ae0a84efd071dda8..5d21c78cc416f552c6693b9e3ea704f319f08164 100644
--- a/lib/SimpleSAML/Metadata/SAMLBuilder.php
+++ b/lib/SimpleSAML/Metadata/SAMLBuilder.php
@@ -118,6 +118,88 @@ class SimpleSAML_Metadata_SAMLBuilder {
 				$e->Extensions[] = $s;
 			}
 		}
+
+		if ($metadata->hasValue('EntityAttributes')) {
+			$ea = new SAML2_XML_mdattr_EntityAttributes();
+			foreach ($metadata->getArray('EntityAttributes') as $attributeName => $attributeValues) {
+				$a = new SAML2_XML_saml_Attribute();
+				$a->Name = $attributeName;
+				$a->NameFormat = 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri';
+
+				// Attribute names that is not URI is prefixed as this: '{nameformat}name'
+				if (preg_match('/^\{(.*?)\}(.*)$/', $attributeName, $matches)) {
+					$a->Name = $matches[2];
+					$nameFormat = $matches[1];
+					if ($nameFormat !== SAML2_Const::NAMEFORMAT_UNSPECIFIED) {
+						$a->NameFormat = $nameFormat;
+					}
+				}
+				foreach ($attributeValues as $attributeValue) {
+					$a->AttributeValue[] = new SAML2_XML_saml_AttributeValue($attributeValue);
+				}
+				$ea->children[] = $a;
+			}
+			$this->entityDescriptor->Extensions[] = $ea;
+		}
+
+		if ($metadata->hasValue('UIInfo')) {
+			$ui = new SAML2_XML_mdui_UIInfo();
+			foreach ($metadata->getArray('UIInfo') as $uiName => $uiValues) {
+				switch ($uiName) {
+					case 'DisplayName':
+						$ui->DisplayName = $uiValues;
+					break;
+					case 'Description':
+						$ui->Description = $uiValues;
+					break;
+					case 'InformationURL':
+						$ui->InformationURL = $uiValues;
+					break;
+					case 'PrivacyStatementURL':
+						$ui->PrivacyStatementURL = $uiValues;
+					break;
+					case 'Keywords':
+						foreach ($uiValues as $lang => $keywords) {
+							$uiItem = new SAML2_XML_mdui_Keywords();
+							$uiItem->lang = $lang;
+							$uiItem->Keywords = $keywords;
+							$ui->Keywords[] = $uiItem;
+						}
+					break;
+					case 'Logo':
+						foreach ($uiValues as $logo) {
+							$uiItem = new SAML2_XML_mdui_Logo();
+							$uiItem->url    = $logo['url'];
+							$uiItem->width  = $logo['width'];
+							$uiItem->height = $logo['height'];
+							if (isset($logo['lang'])) {
+								$uiItem->lang = $logo['lang'];
+							}
+							$ui->Logo[] = $uiItem;
+						}
+					break;
+				}
+			}
+			$e->Extensions[] = $ui;
+		}
+
+		if ($metadata->hasValue('DiscoHints')) {
+			$dh = new SAML2_XML_mdui_DiscoHints();
+			foreach ($metadata->getArray('DiscoHints') as $dhName => $dhValues) {
+				switch ($dhName) {
+					case 'IPHint':
+						$dh->IPHint = $dhValues;
+					break;
+					case 'DomainHint':
+						$dh->DomainHint = $dhValues;
+					break;
+					case 'GeolocationHint':
+						$dh->GeolocationHint = $dhValues;
+					break;
+				}
+			}
+			$e->Extensions[] = $dh;
+		}
 	}
 
 
diff --git a/lib/SimpleSAML/Metadata/SAMLParser.php b/lib/SimpleSAML/Metadata/SAMLParser.php
index f5e089d5568d3ff767a4af6cd8bd129a37bca638..8822451abd3cd5cbf76bcdfbf0d1d16953718e3f 100644
--- a/lib/SimpleSAML/Metadata/SAMLParser.php
+++ b/lib/SimpleSAML/Metadata/SAMLParser.php
@@ -97,6 +97,8 @@ class SimpleSAML_Metadata_SAMLParser {
 	private $entityAttributes;
 	private $attributes;
 	private $tags;
+	private $uiInfo;
+	private $discoHints;
 
 
 	/**
@@ -427,7 +429,14 @@ class SimpleSAML_Metadata_SAMLParser {
 		if (!empty($this->entityAttributes)) {
 			$metadata['EntityAttributes'] = $this->entityAttributes;
 		}
-		
+
+		if (!empty($roleDescriptor['UIInfo'])) {
+			$metadata['UIInfo'] = $roleDescriptor['UIInfo'];
+		}
+
+		if (!empty($roleDescriptor['DiscoHints'])) {
+			$metadata['DiscoHints'] = $roleDescriptor['DiscoHints'];
+		}
 	}
 
 
@@ -739,6 +748,8 @@ class SimpleSAML_Metadata_SAMLParser {
 		$ret['scope'] = $ext['scope'];
 		$ret['tags'] = $ext['tags'];
 		$ret['EntityAttributes'] = $ext['EntityAttributes'];
+		$ret['UIInfo'] = $ext['UIInfo'];
+		$ret['DiscoHints'] = $ext['DiscoHints'];
 
 		return $ret;
 	}
@@ -861,6 +872,8 @@ class SimpleSAML_Metadata_SAMLParser {
 			'scope' => array(),
 			'tags' => array(),
 			'EntityAttributes' => array(),
+			'UIInfo' => array(),
+			'DiscoHints' => array(),
 		);
 
 		foreach ($element->Extensions as $e) {
@@ -869,7 +882,7 @@ class SimpleSAML_Metadata_SAMLParser {
 				$ret['scope'][] = $e->scope;
 				continue;
 			}
-			
+
 			// Entity Attributes are only allowed at entity level extensions
 			// and not at RoleDescriptor level
 			if ($element instanceof SAML2_XML_md_EntityDescriptor) {
@@ -901,7 +914,52 @@ class SimpleSAML_Metadata_SAMLParser {
 					}
 				}				
 			}
-			
+
+			// UIInfo elements are only allowed at RoleDescriptor level extensions
+			if ($element instanceof SAML2_XML_md_RoleDescriptor) {
+
+				if ($e instanceof SAML2_XML_mdui_UIInfo) {
+
+					$ret['UIInfo']['DisplayName']         = $e->DisplayName;
+					$ret['UIInfo']['Description']         = $e->Description;
+					$ret['UIInfo']['InformationURL']      = $e->InformationURL;
+					$ret['UIInfo']['PrivacyStatementURL'] = $e->PrivacyStatementURL;
+
+					foreach($e->Keywords as $uiItem) {
+						if (!($uiItem instanceof SAML2_XML_mdui_Keywords)
+						 || empty($uiItem->Keywords)
+						 || empty($uiItem->lang))
+							continue;
+						$ret['UIInfo']['Keywords'][$uiItem->lang] = $uiItem->Keywords;
+					}
+					foreach($e->Logo as $uiItem) {
+						if (!($uiItem instanceof SAML2_XML_mdui_Logo)
+						 || empty($uiItem->url)
+						 || empty($uiItem->height)
+						 || empty($uiItem->width))
+							continue;
+						$logo = array(
+							'url'    => $uiItem->url,
+							'height' => $uiItem->height,
+							'width'  => $uiItem->width,
+						);
+						if (!empty($uiItem->Lang)) {
+							$logo['lang'] = $uiItem->lang;
+						}
+						$ret['UIInfo']['Logo'][] = $logo;
+					}
+				}
+			}
+
+			// DiscoHints elements are only allowed at IDPSSODescriptor level extensions
+			if ($element instanceof SAML2_XML_md_IDPSSODescriptor) {
+
+				if ($e instanceof SAML2_XML_mdui_DiscoHints) {
+					$ret['DiscoHints']['IPHint']          = $e->IPHint;
+					$ret['DiscoHints']['DomainHint']      = $e->DomainHint;
+					$ret['DiscoHints']['GeolocationHint'] = $e->GeolocationHint;
+				}
+			}
 
 
 			if (!($e instanceof SAML2_XML_Chunk)) {
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index e9ee76513211920742632a4cae8116e0f7d6a83b..e0b8e0db55a0743f08e2ab7f547ae6ddcabb954f 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -105,6 +105,18 @@ try {
 		$metaArray['scope'] = $idpmeta->getArray('scope');
 	}
 
+	if ($idpmeta->hasValue('EntityAttributes')) {
+		$metaArray['EntityAttributes'] = $idpmeta->getArray('EntityAttributes');
+	}
+
+	if ($idpmeta->hasValue('UIInfo')) {
+		$metaArray['UIInfo'] = $idpmeta->getArray('UIInfo');
+	}
+
+	if ($idpmeta->hasValue('DiscoHints')) {
+		$metaArray['DiscoHints'] = $idpmeta->getArray('DiscoHints');
+	}
+
 	$metaflat = '$metadata[' . var_export($idpentityid, TRUE) . '] = ' . var_export($metaArray, TRUE) . ';';
 
 	$metaBuilder = new SimpleSAML_Metadata_SAMLBuilder($idpentityid);