diff --git a/lib/SAML2/SignedElementHelper.php b/lib/SAML2/SignedElementHelper.php
new file mode 100644
index 0000000000000000000000000000000000000000..ecae866c957a8724290287812a2d49d956ef6791
--- /dev/null
+++ b/lib/SAML2/SignedElementHelper.php
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * Helper class for processing signed elements.
+ *
+ * Can either be inherited from, or can be used by proxy.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_SignedElementHelper implements SAML2_SignedElement {
+
+	/**
+	 * The private key we should use to sign the message.
+	 *
+	 * The private key can be NULL, in which case the message is sent unsigned.
+	 *
+	 * @var XMLSecurityKey|NULL
+	 */
+	private $signatureKey;
+
+
+	/**
+	 * List of certificates that should be included in the message.
+	 *
+	 * @var array
+	 */
+	private $certificates;
+
+
+	/**
+	 * Available methods for validating this message.
+	 *
+	 * @var array
+	 */
+	private $validators;
+
+
+	/**
+	 * Initialize the helper class.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element which may be signed.
+	 */
+	protected function __construct(DOMElement $xml = NULL) {
+
+		$this->certificates = array();
+		$this->validators = array();
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		/* Validate the signature element of the message. */
+		try {
+			$sig = SAML2_Utils::validateElement($xml);
+
+			if ($sig !== FALSE) {
+				$this->certificates = $sig['Certificates'];
+				$this->validators[] = array(
+					'Function' => array('SAML2_Utils', 'validateSignature'),
+					'Data' => $sig,
+					);
+			}
+
+		} catch (Exception $e) {
+			/* Ignore signature validation errors. */
+		}
+	}
+
+
+	/**
+	 * Add a method for validating this element.
+	 *
+	 * This function is used for custom validation extensions
+	 *
+	 * @param callback $function  The function which should be called.
+	 * @param mixed $data  The data that should be included as the first parameter to the function.
+	 */
+	public function addValidator($function, $data) {
+		assert('is_callable($function)');
+
+		$this->validators[] = array(
+			'Function' => $function,
+			'Data' => $data,
+			);
+	}
+
+
+	/**
+	 * Validate this element against a public key.
+	 *
+	 * TRUE is returned on success, FALSE is returned if we don't have any
+	 * signature we can validate. An exception is thrown if the signature
+	 * validation fails.
+	 *
+	 * @param XMLSecurityKey $key  The key we should check against.
+	 * @return boolean  TRUE on success, FALSE when we don't have a signature.
+	 */
+	public function validate(XMLSecurityKey $key) {
+
+		if (count($this->validators) === 0) {
+			return FALSE;
+		}
+
+		$exceptions = array();
+
+		foreach ($this->validators as $validator) {
+			$function = $validator['Function'];
+			$data = $validator['Data'];
+
+			try {
+				call_user_func($function, $data, $key);
+				/* We were able to validate the message with this validator. */
+				return TRUE;
+			} catch (Exception $e) {
+				$exceptions[] = $e;
+			}
+		}
+
+		/* No validators were able to validate the message. */
+		throw $exceptions[0];
+	}
+
+
+	/**
+	 * Retrieve the private key we should use to sign the message.
+	 *
+	 * @return XMLSecurityKey|NULL The key, or NULL if no key is specified.
+	 */
+	public function getSignatureKey() {
+		return $this->signatureKey;
+	}
+
+
+	/**
+	 * Set the private key we should use to sign the message.
+	 *
+	 * If the key is NULL, the message will be sent unsigned.
+	 *
+	 * @param XMLSecurityKey|NULL $key
+	 */
+	public function setSignatureKey(XMLsecurityKey $signatureKey = NULL) {
+		$this->signatureKey = $signatureKey;
+	}
+
+
+	/**
+	 * Set the certificates that should be included in the message.
+	 *
+	 * The certificates should be strings with the PEM encoded data.
+	 *
+	 * @param array $certificates  An array of certificates.
+	 */
+	public function setCertificates(array $certificates) {
+		$this->certificates = $certificates;
+	}
+
+
+	/**
+	 * Retrieve the certificates that are included in the message.
+	 *
+	 * @return array  An array of certificates.
+	 */
+	public function getCertificates() {
+		return $this->certificates;
+	}
+
+
+	/**
+	 * Retrieve certificates that sign this element.
+	 *
+	 * @return array  Array with certificates.
+	 */
+	public function getValidatingCertificates() {
+
+		$ret = array();
+		foreach ($this->certificates as $cert) {
+
+			/* We have found a matching fingerprint. */
+			$pemCert = "-----BEGIN CERTIFICATE-----\n" .
+				chunk_split($cert, 64) .
+				"-----END CERTIFICATE-----\n";
+
+			/* Extract the public key from the certificate for validation. */
+			$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type'=>'public'));
+			$key->loadKey($pemCert);
+
+			/* Check the signature. */
+			if ($this->validate($key)) {
+				$ret[] = $cert;
+			}
+		}
+
+		return $ret;
+	}
+
+
+	/**
+	 * Sign the given XML element.
+	 *
+	 * @param DOMElement $root  The element we should sign.
+	 * @param DOMElement|NULL $insertBefore  The element we should insert the signature node before.
+	 */
+	protected function signElement(DOMElement $root, DOMElement $insertBefore = NULL) {
+
+		if ($this->signatureKey === NULL) {
+			/* We cannot sign this element. */
+			return;
+		}
+
+		SAML2_Utils::insertSignature($this->signatureKey, $this->certificates, $root, $insertBefore);
+
+		return $root;
+	}
+
+}
diff --git a/lib/SAML2/XML/Chunk.php b/lib/SAML2/XML/Chunk.php
new file mode 100644
index 0000000000000000000000000000000000000000..fb4792867baab5632c8bdeeb0cd487a5a098b879
--- /dev/null
+++ b/lib/SAML2/XML/Chunk.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * Serializable class used to hold an XML element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_Chunk {
+
+	/**
+	 * The localName of the element.
+	 *
+	 * @var string
+	 */
+	public $localName;
+
+
+	/**
+	 * The namespaceURI of this element.
+	 *
+	 * @var string
+	 */
+	public $namespaceURI;
+
+
+	/**
+	 * The DOMElement we contain.
+	 *
+	 * @var DOMElement
+	 */
+	private $xml;
+
+
+	/**
+	 * The DOMElement as a text string. Used during serialization.
+	 *
+	 * @var string|NULL
+	 */
+	private $xmlString;
+
+
+	/**
+	 * Create a XMLChunk from a copy of the given DOMElement.
+	 *
+	 * @param DOMElement $xml  The element we should copy.
+	 */
+	public function __construct(DOMElement $xml) {
+
+		$this->localName = $xml->localName;
+		$this->namespaceURI = $xml->namespaceURI;
+
+		$this->xml = SAML2_Utils::copyElement($xml);
+	}
+
+
+	/**
+	 * Get this DOMElement.
+	 *
+	 * @return DOMElement  This element.
+	 */
+	public function getXML() {
+		assert('$this->xml instanceof DOMElement || is_string($this->xmlString)');
+
+		if ($this->xml === NULL) {
+			$doc = new DOMDocument();
+			$doc->loadXML($this->xmlString);
+			$this->xml = $doc->firstChild;
+		}
+
+		return $this->xml;
+	}
+
+
+	/**
+	 * Append this XML element to a different XML element.
+	 *
+	 * @param DOMElement $parent  The element we should append this element to.
+	 * @return DOMElement  The new element.
+	 */
+	public function toXML(DOMElement $parent) {
+
+		return SAML2_Utils::copyElement($this->getXML(), $parent);
+	}
+
+
+	/**
+	 * Serialization handler.
+	 *
+	 * Converts the XML data to a string that can be serialized
+	 *
+	 * @return array  List of properties that should be serialized.
+	 */
+	public function __sleep() {
+		assert('$this->xml instanceof DOMElement || is_string($this->xmlString)');
+
+		if ($this->xmlString === NULL) {
+			$this->xmlString = $this->xml->ownerDocument->saveXML($this->xml);
+		}
+
+		return array('xmlString');
+	}
+
+}
diff --git a/lib/SAML2/XML/ds/KeyInfo.php b/lib/SAML2/XML/ds/KeyInfo.php
new file mode 100644
index 0000000000000000000000000000000000000000..44b4b0d0ed3af3e48392a45d0066400a1387388a
--- /dev/null
+++ b/lib/SAML2/XML/ds/KeyInfo.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Class representing a ds:KeyInfo element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_ds_KeyInfo {
+
+	/**
+	 * The Id attribute on this element.
+	 *
+	 * @var string|NULL
+	 */
+	public $Id = NULL;
+
+
+	/**
+	 * The various key information elements.
+	 *
+	 * Array with various elements describing this key.
+	 * Unknown elements will be represented by SAML2_XML_Chunk.
+	 *
+	 * @var array
+	 */
+	public $info = array();
+
+
+	/**
+	 * Initialize a KeyInfo element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if ($xml->hasAttribute('Id')) {
+			$this->Id = $xml->getAttribute('Id');
+		}
+
+		for ($n = $xml->firstChild; $n !== NULL; $n = $n->nextSibling) {
+			if (!($n instanceof DOMElement)) {
+				continue;
+			}
+
+			if ($n->namespaceURI !== XMLSecurityDSig::XMLDSIGNS) {
+				$this->info[] = new SAML2_XML_Chunk($n);
+				continue;
+			}
+			switch ($n->localName) {
+			case 'KeyName':
+				$this->info[] = new SAML2_XML_ds_KeyName($n);
+				break;
+			case 'X509Data':
+				$this->info[] = new SAML2_XML_ds_X509Data($n);
+				break;
+			default:
+				$this->info[] = new SAML2_XML_Chunk($n);
+				break;
+			}
+		}
+	}
+
+
+	/**
+	 * Convert this KeyInfo to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this KeyInfo to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_null($this->Id) || is_string($this->Id)');
+		assert('is_array($this->info)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:KeyInfo');
+		$parent->appendChild($e);
+
+		if (isset($this->Id)) {
+			$e->setAttribute('Id', $this->Id);
+		}
+
+		foreach ($this->info as $n) {
+			$n->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/ds/KeyName.php b/lib/SAML2/XML/ds/KeyName.php
new file mode 100644
index 0000000000000000000000000000000000000000..6eae3a4f2addb5cbe4304495da5ac2f4d0f6334e
--- /dev/null
+++ b/lib/SAML2/XML/ds/KeyName.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Class representing a ds:KeyName element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_ds_KeyName {
+
+	/**
+	 * The key name.
+	 *
+	 * @var string
+	 */
+	public $name;
+
+
+	/**
+	 * Initialize a KeyName element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->name = $xml->textContent;
+	}
+
+
+	/**
+	 * Convert this KeyName element to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this KeyName element to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->name)');
+
+		return SAML2_Utils::addString($parent, XMLSecurityDSig::XMLDSIGNS, 'ds:KeyName', $this->name);
+	}
+
+}
diff --git a/lib/SAML2/XML/ds/X509Certificate.php b/lib/SAML2/XML/ds/X509Certificate.php
new file mode 100644
index 0000000000000000000000000000000000000000..c4dcac197951b0e1c87b28814f845d45acf90c87
--- /dev/null
+++ b/lib/SAML2/XML/ds/X509Certificate.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Class representing a ds:X509Certificate element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_ds_X509Certificate {
+
+	/**
+	 * The base64-encoded certificate.
+	 *
+	 * @var string
+	 */
+	public $certificate;
+
+
+	/**
+	 * Initialize an X509Certificate element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->certificate = $xml->textContent;
+	}
+
+
+	/**
+	 * Convert this X509Certificate element to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this X509Certificate element to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->certificate)');
+
+		return SAML2_Utils::addString($parent, XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $this->certificate);
+	}
+
+}
diff --git a/lib/SAML2/XML/ds/X509Data.php b/lib/SAML2/XML/ds/X509Data.php
new file mode 100644
index 0000000000000000000000000000000000000000..e6b3c066f59461fb335cd337ee87a08ae19c364c
--- /dev/null
+++ b/lib/SAML2/XML/ds/X509Data.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Class representing a ds:X509Data element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_ds_X509Data {
+
+	/**
+	 * The various X509 data elements.
+	 *
+	 * Array with various elements describing this certificate.
+	 * Unknown elements will be represented by SAML2_XML_Chunk.
+	 *
+	 * @var array
+	 */
+	public $data = array();
+
+
+	/**
+	 * Initialize a X509Data.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		for ($n = $xml->firstChild; $n !== NULL; $n = $n->nextSibling) {
+			if (!($n instanceof DOMElement)) {
+				continue;
+			}
+
+			if ($n->namespaceURI !== XMLSecurityDSig::XMLDSIGNS) {
+				$this->data[] = new SAML2_XML_Chunk($n);
+				continue;
+			}
+			switch ($n->localName) {
+			case 'X509Certificate':
+				$this->data[] = new SAML2_XML_ds_X509Certificate($n);
+				break;
+			default:
+				$this->data[] = new SAML2_XML_Chunk($n);
+				break;
+			}
+		}
+	}
+
+
+	/**
+	 * Convert this X509Data element to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this X509Data element to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->data)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Data');
+		$parent->appendChild($e);
+
+		foreach ($this->data as $n) {
+			$n->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/AdditionalMetadataLocation.php b/lib/SAML2/XML/md/AdditionalMetadataLocation.php
new file mode 100644
index 0000000000000000000000000000000000000000..3bdb6ba72354877bc177ca4a289f8b6b2a7a0d70
--- /dev/null
+++ b/lib/SAML2/XML/md/AdditionalMetadataLocation.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * Class representing SAML 2 metadata AdditionalMetadataLocation element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_AdditionalMetadataLocation {
+
+	/**
+	 * The namespace of this metadata.
+	 *
+	 * @var string
+	 */
+	public $namespace;
+
+	/**
+	 * The URI where the metadata is located.
+	 *
+	 * @var string
+	 */
+	public $location;
+
+
+	/**
+	 * Initialize an AdditionalMetadataLocation element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('namespace')) {
+			throw new Exception('Missing namespace attribute on AdditionalMetadataLocation element.');
+		}
+		$this->namespace = $xml->getAttribute('namespace');
+
+		$this->location = $xml->textContent;
+	}
+
+
+	/**
+	 * Convert this AdditionalMetadataLocation to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 * @return DOMElement  This AdditionalMetadataLocation-element.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->namespace)');
+		assert('is_string($this->location)');
+
+		$e = SAML2_Utils::addString($parent, SAML2_Const::NS_MD, 'md:AdditionalMetadataLocation', $this->location);
+		$e->setAttribute('namespace', $this->namespace);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/AffiliationDescriptor.php b/lib/SAML2/XML/md/AffiliationDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..ad333230586705a347f589e6a807aff0cde20de0
--- /dev/null
+++ b/lib/SAML2/XML/md/AffiliationDescriptor.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * Class representing SAML 2 AffiliationDescriptor element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_AffiliationDescriptor extends SAML2_SignedElementHelper {
+
+	/**
+	 * The affiliationOwnerID.
+	 *
+	 * @var string
+	 */
+	public $affiliationOwnerID;
+
+
+	/**
+	 * The ID of this element.
+	 *
+	 * @var string|NULL
+	 */
+	public $ID;
+
+
+	/**
+	 * How long this element is valid, as a unix timestamp.
+	 *
+	 * @var int|NULL
+	 */
+	public $validUntil;
+
+
+	/**
+	 * The length of time this element can be cached, as string.
+	 *
+	 * @var string|NULL
+	 */
+	public $cacheDuration;
+
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * The AffiliateMember(s).
+	 *
+	 * Array of entity ID strings.
+	 *
+	 * @var array
+	 */
+	public $AffiliateMember = array();
+
+
+	/**
+	 * KeyDescriptor elements.
+	 *
+	 * Array of SAML2_XML_md_KeyDescriptor elements.
+	 *
+	 * @var array
+	 */
+	public $KeyDescriptor = array();
+
+
+	/**
+	 * Initialize a AffiliationDescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		parent::__construct($xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('affiliationOwnerID')) {
+			throw new Exception('Missing affiliationOwnerID on AffiliationDescriptor.');
+		}
+		$this->affiliationOwnerID = $xml->getAttribute('affiliationOwnerID');
+
+		if ($xml->hasAttribute('ID')) {
+			$this->ID = $xml->getAttribute('ID');
+		}
+
+		if ($xml->hasAttribute('validUntil')) {
+			$this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil'));
+		}
+
+		if ($xml->hasAttribute('cacheDuration')) {
+			$this->cacheDuration = $xml->getAttribute('cacheDuration');
+		}
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+		$this->AffiliateMember = SAML2_Utils::extractStrings($xml, './saml_metadata:AffiliateMember');
+		if (empty($this->AffiliateMember)) {
+			throw new Exception('Missing AffiliateMember in AffiliationDescriptor.');
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:KeyDescriptor') as $kd) {
+			$this->KeyDescriptor[] = new SAML2_XML_md_KeyDescriptor($kd);
+		}
+	}
+
+
+	/**
+	 * Add this AffiliationDescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this endpoint to.
+	 * @param string $name  The name of the element we should create.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->affiliationOwnerID)');
+		assert('is_null($this->ID) || is_string($this->ID)');
+		assert('is_null($this->validUntil) || is_int($this->validUntil)');
+		assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)');
+		assert('is_array($this->Extensions)');
+		assert('is_array($this->AffiliateMember)');
+		assert('!empty($this->AffiliateMember)');
+		assert('is_array($this->KeyDescriptor)');
+
+		$e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:AffiliationDescriptor');
+		$parent->appendChild($e);
+
+		$e->setAttribute('affiliationOwnerID', $this->affiliationOwnerID);
+
+		if (isset($this->ID)) {
+			$e->setAttribute('ID', $this->ID);
+		}
+
+		if (isset($this->validUntil)) {
+			$e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil));
+		}
+
+		if (isset($this->cacheDuration)) {
+			$e->setAttribute('cacheDuration', $this->cacheDuration);
+		}
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AffiliateMember', FALSE, $this->AffiliateMember);
+
+		foreach ($this->KeyDescriptor as $kd) {
+			$kd->toXML($e);
+		}
+
+		$this->signElement($e, $e->firstChild);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php b/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..bbf95075052bf02789a19bf12a0f719471e6dd82
--- /dev/null
+++ b/lib/SAML2/XML/md/AttributeAuthorityDescriptor.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Class representing SAML 2 metadata AttributeAuthorityDescriptor.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_AttributeAuthorityDescriptor extends SAML2_XML_md_RoleDescriptor {
+
+	/**
+	 * List of AttributeService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AttributeService = array();
+
+
+	/**
+	 * List of AssertionIDRequestService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AssertionIDRequestService = array();
+
+
+	/**
+	 * List of supported NameID formats.
+	 *
+	 * Array of strings.
+	 *
+	 * @var array
+	 */
+	public $NameIDFormat = array();
+
+
+	/**
+	 * List of supported attribute profiles.
+	 *
+	 * Array with strings.
+	 *
+	 * @var array
+	 */
+	public $AttributeProfile = array();
+
+
+	/**
+	 * List of supported attributes.
+	 *
+	 * Array with SAML2_XML_saml_Attribute objects.
+	 *
+	 * @var array
+	 */
+	public $Attribute = array();
+
+
+	/**
+	 * Initialize an IDPSSODescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('md:AttributeAuthorityDescriptor', $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AttributeService') as $ep) {
+			$this->AttributeService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+		if (empty($this->AttributeService)) {
+			throw new Exception('Must have at least one AttributeService in AttributeAuthorityDescriptor.');
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) {
+			$this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs);
+		}
+
+		$this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat');
+
+		$this->AttributeProfile = SAML2_Utils::extractStrings($xml, './saml_metadata:AttributeProfile');
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute') as $a) {
+			$this->Attribute[] = new SAML2_XML_saml_Attribute($a);
+		}
+	}
+
+
+	/**
+	 * Add this AttributeAuthorityDescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this IDPSSODescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->AttributeService)');
+		assert('!empty($this->AttributeService)');
+		assert('is_array($this->AssertionIDRequestService)');
+		assert('is_array($this->NameIDFormat)');
+		assert('is_array($this->AttributeProfile)');
+		assert('is_array($this->Attribute)');
+
+		$e = parent::toXML($parent);
+
+		foreach ($this->AttributeService as $ep) {
+			$ep->toXML($e, 'md:AttributeService');
+		}
+
+		foreach ($this->AssertionIDRequestService as $ep) {
+			$ep->toXML($e, 'md:AssertionIDRequestService');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat);
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AttributeProfile', FALSE, $this->AttributeProfile);
+
+		foreach ($this->Attribute as $a) {
+			$a->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/AttributeConsumingService.php b/lib/SAML2/XML/md/AttributeConsumingService.php
new file mode 100644
index 0000000000000000000000000000000000000000..3e0b6a3dea16cbdbedc2c85d0052b30f83ab8ceb
--- /dev/null
+++ b/lib/SAML2/XML/md/AttributeConsumingService.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * Class representing SAML 2 Metadata AttributeConsumingService element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_AttributeConsumingService {
+
+	/**
+	 * The index of this AttributeConsumingService.
+	 *
+	 * @var int
+	 */
+	public $index;
+
+
+	/**
+	 * Whether this is the default AttributeConsumingService.
+	 *
+	 * @var bool|NULL
+	 */
+	public $isDefault = NULL;
+
+
+	/**
+	 * The ServiceName of this AttributeConsumingService.
+	 *
+	 * This is an associative array with language => translation.
+	 *
+	 * @var array
+	 */
+	public $ServiceName = array();
+
+
+	/**
+	 * The ServiceDescription of this AttributeConsumingService.
+	 *
+	 * This is an associative array with language => translation.
+	 *
+	 * @var array
+	 */
+	public $ServiceDescription = array();
+
+
+	/**
+	 * The RequestedAttribute elements.
+	 *
+	 * This is an array of SAML_RequestedAttributeType elements.
+	 *
+	 * @var array
+	 */
+	public $RequestedAttribute = array();
+
+
+	/**
+	 * Initialize / parse an AttributeConsumingService.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+
+		if (!$xml->hasAttribute('index')) {
+			throw new Exception('Missing index on AttributeConsumingService.');
+		}
+		$this->index = (int)$xml->getAttribute('index');
+
+		$this->isDefault = SAML2_Utils::parseBoolean($xml, 'isDefault', NULL);
+
+		$this->ServiceName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:ServiceName');
+		if (empty($this->ServiceName)) {
+			throw new Exception('Missing ServiceName in AttributeConsumingService.');
+		}
+
+		$this->ServiceDescription = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:ServiceDescription');
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:RequestedAttribute') as $ra) {
+			$this->RequestedAttribute[] = new SAML2_XML_md_RequestedAttribute($ra);
+		}
+	}
+
+
+	/**
+	 * Convert to DOMElement.
+	 *
+	 * @param DOMElement $parent  The element we should append this AttributeConsumingService to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_int($this->index)');
+		assert('is_null($this->isDefault) || is_bool($this->isDefault)');
+		assert('is_array($this->ServiceName)');
+		assert('is_array($this->ServiceDescription)');
+		assert('is_array($this->RequestedAttribute)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:AttributeConsumingService');
+		$parent->appendChild($e);
+
+		$e->setAttribute('index', (string)$this->index);
+
+		if ($this->isDefault === TRUE) {
+			$e->setAttribute('isDefault', 'true');
+		} elseif ($this->isDefault === FALSE) {
+			$e->setAttribute('isDefault', 'false');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:ServiceName', TRUE, $this->ServiceName);
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:ServiceDescription', TRUE, $this->ServiceDescription);
+
+		foreach ($this->RequestedAttribute as $ra) {
+			$ra->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php b/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..9a059823fcc3bd1ebef1b4915c604abdf80afcd8
--- /dev/null
+++ b/lib/SAML2/XML/md/AuthnAuthorityDescriptor.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Class representing SAML 2 metadata AuthnAuthorityDescriptor.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_AuthnAuthorityDescriptor extends SAML2_XML_md_RoleDescriptor {
+
+	/**
+	 * List of AuthnQueryService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AuthnQueryService = array();
+
+
+	/**
+	 * List of AssertionIDRequestService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AssertionIDRequestService = array();
+
+
+	/**
+	 * List of supported NameID formats.
+	 *
+	 * Array of strings.
+	 *
+	 * @var array
+	 */
+	public $NameIDFormat = array();
+
+
+	/**
+	 * Initialize an IDPSSODescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('md:AuthnAuthorityDescriptor', $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AuthnQueryService') as $ep) {
+			$this->AuthnQueryService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+		if (empty($this->AuthnQueryService)) {
+			throw new Exception('Must have at least one AuthnQueryService in AuthnAuthorityDescriptor.');
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) {
+			$this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs);
+		}
+
+		$this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat');
+	}
+
+
+	/**
+	 * Add this IDPSSODescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this AuthnAuthorityDescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->AuthnQueryService)');
+		assert('!empty($this->AuthnQueryService)');
+		assert('is_array($this->AssertionIDRequestService)');
+		assert('is_array($this->NameIDFormat)');
+
+		$e = parent::toXML($parent);
+
+		foreach ($this->AuthnQueryService as $ep) {
+			$ep->toXML($e, 'md:AuthnQueryService');
+		}
+
+		foreach ($this->AssertionIDRequestService as $ep) {
+			$ep->toXML($e, 'md:AssertionIDRequestService');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/ContactPerson.php b/lib/SAML2/XML/md/ContactPerson.php
new file mode 100644
index 0000000000000000000000000000000000000000..ea347c3f908ff0724357604b8da8fa6c6160c5e2
--- /dev/null
+++ b/lib/SAML2/XML/md/ContactPerson.php
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * Class representing SAML 2 ContactPerson.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_ContactPerson {
+
+	/**
+	 * The contact type.
+	 *
+	 * @var string
+	 */
+	public $contactType;
+
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * The Company of this contact.
+	 *
+	 * @var string
+	 */
+	public $Company = NULL;
+
+
+	/**
+	 * The GivenName of this contact.
+	 *
+	 * @var string
+	 */
+	public $GivenName = NULL;
+
+
+	/**
+	 * The SurName of this contact.
+	 *
+	 * @var string
+	 */
+	public $SurName = NULL;
+
+
+	/**
+	 * The EmailAddresses of this contact.
+	 *
+	 * @var array
+	 */
+	public $EmailAddress = array();
+
+
+	/**
+	 * The TelephoneNumbers of this contact.
+	 *
+	 * @var array
+	 */
+	public $TelephoneNumber = array();
+
+
+	/**
+	 * Initialize a ContactPerson element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('contactType')) {
+			throw new Exception('Missing contactType on ContactPerson.');
+		}
+		$this->contactType = $xml->getAttribute('contactType');
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+
+		$this->Company = self::getStringElement($xml, 'Company');
+		$this->GivenName = self::getStringElement($xml, 'GivenName');
+		$this->SurName = self::getStringElement($xml, 'SurName');
+		$this->EmailAddress = self::getStringElements($xml, 'EmailAddress');
+		$this->TelephoneNumber = self::getStringElements($xml, 'TelephoneNumber');
+	}
+
+
+	/**
+	 * Retrieve the value of a child DOMElements as an array of strings.
+	 *
+	 * @param DOMElement $parent  The parent element.
+	 * @param string $name  The name of the child elements.
+	 * @return array  The value of the child elements.
+	 */
+	private static function getStringElements(DOMElement $parent, $name) {
+		assert('is_string($name)');
+
+		$e = SAML2_Utils::xpQuery($parent, './saml_metadata:' . $name);
+
+		$ret = array();
+		foreach ($e as $i) {
+			$ret[] = $i->textContent;
+		}
+
+		return $ret;
+	}
+
+
+	/**
+	 * Retrieve the value of a child DOMElement as a string.
+	 *
+	 * @param DOMElement $parent  The parent element.
+	 * @param string $name  The name of the child element.
+	 * @return string|NULL  The value of the child element.
+	 */
+	private static function getStringElement(DOMElement $parent, $name) {
+		assert('is_string($name)');
+
+		$e = self::getStringElements($parent, $name);
+		if (empty($e)) {
+			return NULL;
+		}
+		if (count($e) > 1) {
+			throw new Exception('More than one ' . $name . ' in ' . $parent->tagName);
+		}
+
+		return $e[0];
+	}
+
+
+	/**
+	 * Convert this ContactPerson to XML.
+	 *
+	 * @param DOMElement $parent  The element we should add this contact to.
+	 * @return DOMElement  The new ContactPerson-element.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->contactType)');
+		assert('is_array($this->Extensions)');
+		assert('is_null($this->Company) || is_string($this->Company)');
+		assert('is_null($this->GivenName) || is_string($this->GivenName)');
+		assert('is_null($this->SurName) || is_string($this->SurName)');
+		assert('is_array($this->EmailAddress)');
+		assert('is_array($this->TelephoneNumber)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:ContactPerson');
+		$parent->appendChild($e);
+
+		$e->setAttribute('contactType', $this->contactType);
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		if (isset($this->Company)) {
+			SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:Company', $this->Company);
+		}
+		if (isset($this->GivenName)) {
+			SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:GivenName', $this->GivenName);
+		}
+		if (isset($this->SurName)) {
+			SAML2_Utils::addString($e, SAML2_Const::NS_MD, 'md:SurName', $this->SurName);
+		}
+		if (!empty($this->EmailAddress)) {
+			SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:EmailAddress', FALSE, $this->EmailAddress);
+		}
+		if (!empty($this->TelephoneNumber)) {
+			SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:TelephoneNumber', FALSE, $this->TelephoneNumber);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/EndpointType.php b/lib/SAML2/XML/md/EndpointType.php
new file mode 100644
index 0000000000000000000000000000000000000000..519a0a7d37f933a0d7d8c9973eb7d9fde11ee654
--- /dev/null
+++ b/lib/SAML2/XML/md/EndpointType.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * Class representing SAML 2 EndpointType.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_EndpointType {
+
+	/**
+	 * The binding for this endpoint.
+	 *
+	 * @var string
+	 */
+	public $Binding;
+
+
+	/**
+	 * The URI to this endpoint.
+	 *
+	 * @var string
+	 */
+	public $Location;
+
+
+	/**
+	 * The URI where responses can be delivered.
+	 *
+	 * @var string|NULL
+	 */
+	public $ResponseLocation = NULL;
+
+
+	/**
+	 * Initialize an EndpointType.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('Binding')) {
+			throw new Exception('Missing Binding on ' . $xml->tagName);
+		}
+		$this->Binding = $xml->getAttribute('Binding');
+
+		if (!$xml->hasAttribute('Location')) {
+			throw new Exception('Missing Location on ' . $xml->tagName);
+		}
+		$this->Location = $xml->getAttribute('Location');
+
+		if ($xml->hasAttribute('ResponseLocation')) {
+			$this->ResponseLocation = $xml->getAttribute('ResponseLocation');
+		}
+	}
+
+
+	/**
+	 * Add this endpoint to an XML element.
+	 *
+	 * @param DOMElement $parent  The element we should append this endpoint to.
+	 * @param string $name  The name of the element we should create.
+	 */
+	public function toXML(DOMElement $parent, $name) {
+		assert('is_string($name)');
+		assert('is_string($this->Binding)');
+		assert('is_string($this->Location)');
+		assert('is_null($this->ResponseLocation) || is_string($this->ResponseLocation)');
+
+		$e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, $name);
+		$parent->appendChild($e);
+
+		$e->setAttribute('Binding', $this->Binding);
+		$e->setAttribute('Location', $this->Location);
+
+		if (isset($this->ResponseLocation)) {
+			$e->setAttribute('ResponseLocation', $this->ResponseLocation);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/EntitiesDescriptor.php b/lib/SAML2/XML/md/EntitiesDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6b66b88b520f1cfe2ba49ffaa26b5a3bbcc4587
--- /dev/null
+++ b/lib/SAML2/XML/md/EntitiesDescriptor.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * Class representing SAML 2 EntitiesDescriptor element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_EntitiesDescriptor extends SAML2_SignedElementHelper {
+
+	/**
+	 * The ID of this element.
+	 *
+	 * @var string|NULL
+	 */
+	public $ID;
+
+
+	/**
+	 * How long this element is valid, as a unix timestamp.
+	 *
+	 * @var int|NULL
+	 */
+	public $validUntil;
+
+
+	/**
+	 * The length of time this element can be cached, as string.
+	 *
+	 * @var string|NULL
+	 */
+	public $cacheDuration;
+
+
+	/**
+	 * The name of this entity collection.
+	 *
+	 * @var string|NULL
+	 */
+	public $Name;
+
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * Child EntityDescriptor and EntitiesDescriptor elements.
+	 *
+	 * @var array
+	 */
+	public $children = array();
+
+
+	/**
+	 * Initialize an EntitiesDescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct($xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if ($xml->hasAttribute('ID')) {
+			$this->ID = $xml->getAttribute('ID');
+		}
+		if ($xml->hasAttribute('validUntil')) {
+			$this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil'));
+		}
+		if ($xml->hasAttribute('cacheDuration')) {
+			$this->cacheDuration = $xml->getAttribute('cacheDuration');
+		}
+		if ($xml->hasAttribute('Name')) {
+			$this->Name = $xml->getAttribute('Name');
+		}
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:EntityDescriptor|./saml_metadata:EntitiesDescriptor') as $node) {
+			if ($node->localName === 'EntityDescriptor') {
+				$this->children[] = new SAML2_XML_md_EntityDescriptor($node);
+			} else {
+				$this->children[] = new SAML2_XML_md_EntitiesDescriptor($node);
+			}
+		}
+	}
+
+
+	/**
+	 * Convert this EntitiesDescriptor to XML.
+	 *
+	 * @param DOMElement|NULL $parent  The EntitiesDescriptor we should append this EntitiesDescriptor to.
+	 */
+	public function toXML(DOMElement $parent = NULL) {
+		assert('is_null($this->ID) || is_string($this->ID)');
+		assert('is_null($this->validUntil) || is_int($this->validUntil)');
+		assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)');
+		assert('is_array($this->Extensions)');
+		assert('is_array($this->children)');
+
+		if ($parent === NULL) {
+			$doc = new DOMDocument();
+			$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:EntitiesDescriptor');
+		} else {
+			$e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:EntitiesDescriptor');
+			$parent->appendChild($e);
+		}
+
+		if (isset($this->ID)) {
+			$e->setAttribute('ID', $this->ID);
+		}
+
+		if (isset($this->validUntil)) {
+			$e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil));
+		}
+
+		if (isset($this->cacheDuration)) {
+			$e->setAttribute('cacheDuration', $this->cacheDuration);
+		}
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		foreach ($this->children as $node) {
+			$node->toXML($e);
+		}
+
+		$this->signElement($e, $e->firstChild);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/EntityDescriptor.php b/lib/SAML2/XML/md/EntityDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..bd35f144ee545dbd5335e276eece343c93af65e7
--- /dev/null
+++ b/lib/SAML2/XML/md/EntityDescriptor.php
@@ -0,0 +1,251 @@
+<?php
+
+/**
+ * Class representing SAML 2 EntityDescriptor element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_EntityDescriptor extends SAML2_SignedElementHelper {
+
+	/**
+	 * The entityID this EntityDescriptor represents.
+	 *
+	 * @var string
+	 */
+	public $entityID;
+
+
+	/**
+	 * The ID of this element.
+	 *
+	 * @var string|NULL
+	 */
+	public $ID;
+
+
+	/**
+	 * How long this element is valid, as a unix timestamp.
+	 *
+	 * @var int|NULL
+	 */
+	public $validUntil;
+
+
+	/**
+	 * The length of time this element can be cached, as string.
+	 *
+	 * @var string|NULL
+	 */
+	public $cacheDuration;
+
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * Array with all roles for this entity.
+	 *
+	 * Array of SAML2_XML_md_RoleDescriptor objects (and subclasses of RoleDescriptor).
+	 *
+	 * @var array
+	 */
+	public $RoleDescriptor = array();
+
+
+	/**
+	 * AffiliationDescriptor of this entity.
+	 *
+	 * @var SAML2_XML_md_AffiliationDescriptor|NULL
+	 */
+	public $AffiliationDescriptor = NULL;
+
+
+	/**
+	 * Organization of this entity.
+	 *
+	 * @var SAML2_XML_md_Organization|NULL
+	 */
+	public $Organization = NULL;
+
+
+	/**
+	 * ContactPerson elements for this entity.
+	 *
+	 * @var array
+	 */
+	public $ContactPerson = array();
+
+
+	/**
+	 * AdditionalMetadataLocation elements for this entity.
+	 *
+	 * @var array
+	 */
+	public $AdditionalMetadataLocation = array();
+
+
+	/**
+	 * Initialize an EntitiyDescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct($xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('entityID')) {
+			throw new Exception('Missing required attribute entityID on EntityDescriptor.');
+		}
+		$this->entityID = $xml->getAttribute('entityID');
+
+		if ($xml->hasAttribute('ID')) {
+			$this->ID = $xml->getAttribute('ID');
+		}
+		if ($xml->hasAttribute('validUntil')) {
+			$this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil'));
+		}
+		if ($xml->hasAttribute('cacheDuration')) {
+			$this->cacheDuration = $xml->getAttribute('cacheDuration');
+		}
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+		for ($node = $xml->firstChild; $node !== NULL; $node = $node->nextSibling) {
+			if (!($node instanceof DOMElement)) {
+				continue;
+			}
+
+			if ($node->namespaceURI !== SAML2_Const::NS_MD) {
+				continue;
+			}
+
+			switch ($node->localName) {
+			case 'RoleDescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_UnknownRoleDescriptor($node);
+				break;
+			case 'IDPSSODescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_IDPSSODescriptor($node);
+				break;
+			case 'SPSSODescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_SPSSODescriptor($node);
+				break;
+			case 'AuthnAuthorityDescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_AuthnAuthorityDescriptor($node);
+				break;
+			case 'AttributeAuthorityDescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_AttributeAuthorityDescriptor($node);
+				break;
+			case 'PDPDescriptor':
+				$this->RoleDescriptor[] = new SAML2_XML_md_PDPDescriptor($node);
+				break;
+			}
+		}
+
+		$affiliationDescriptor = SAML2_Utils::xpQuery($xml, './saml_metadata:AffiliationDescriptor');
+		if (count($affiliationDescriptor) > 1) {
+			throw new Exception('More than one AffiliationDescriptor in the entity.');
+		} elseif (!empty($affiliationDescriptor)) {
+			$this->AffiliationDescriptor = new SAML2_XML_md_AffiliationDescriptor($affiliationDescriptor[0]);
+		}
+
+		if (empty($this->RoleDescriptor) && is_null($this->AffiliationDescriptor)) {
+			throw new Exception('Must have either one of the RoleDescriptors or an AffiliationDescriptor in EntityDescriptor.');
+		} elseif (!empty($this->RoleDescriptor) && !is_null($this->AffiliationDescriptor)) {
+			throw new Exception('AffiliationDescriptor cannot be combined with other RoleDescriptor elements in EntityDescriptor.');
+		}
+
+		$organization = SAML2_Utils::xpQuery($xml, './saml_metadata:Organization');
+		if (count($organization) > 1) {
+			throw new Exception('More than one Organization in the entity.');
+		} elseif (!empty($organization)) {
+			$this->Organization = new SAML2_XML_md_Organization($organization[0]);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ContactPerson') as $cp) {
+			$this->ContactPerson[] = new SAML2_XML_md_ContactPerson($cp);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AdditionalMetadataLocation') as $aml) {
+			$this->AdditionalMetadataLocation[] = new SAML2_XML_md_AdditionalMetadataLocation($aml);
+		}
+	}
+
+
+	/**
+	 * Create this EntityDescriptor.
+	 *
+	 * @param DOMElement|NULL $parent  The EntitiesDescriptor we should append this EntityDescriptor to.
+	 */
+	public function toXML(DOMElement $parent = NULL) {
+		assert('is_string($this->entityID)');
+		assert('is_null($this->ID) || is_string($this->ID)');
+		assert('is_null($this->validUntil) || is_int($this->validUntil)');
+		assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)');
+		assert('is_array($this->Extensions)');
+		assert('is_array($this->RoleDescriptor)');
+		assert('is_null($this->AffiliationDescriptor) || $this->AffiliationDescriptor instanceof SAML2_XML_md_AffiliationDescriptor');
+		assert('is_null($this->Organization) || $this->Organization instanceof SAML2_XML_md_Organization');
+		assert('is_array($this->ContactPerson)');
+		assert('is_array($this->AdditionalMetadataLocation)');
+
+		if ($parent === NULL) {
+			$doc = new DOMDocument();
+			$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:EntityDescriptor');
+		} else {
+			$e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:EntityDescriptor');
+			$parent->appendChild($e);
+		}
+
+		$e->setAttribute('entityID', $this->entityID);
+
+		if (isset($this->ID)) {
+			$e->setAttribute('ID', $this->ID);
+		}
+
+		if (isset($this->validUntil)) {
+			$e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil));
+		}
+
+		if (isset($this->cacheDuration)) {
+			$e->setAttribute('cacheDuration', $this->cacheDuration);
+		}
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		foreach ($this->RoleDescriptor as $n) {
+			$n->toXML($e);
+		}
+
+		if (isset($this->AffiliationDescriptor)) {
+			$this->AffiliationDescriptor->toXML($e);
+		}
+
+		if (isset($this->Organization)) {
+			$this->Organization->toXML($e);
+		}
+
+		foreach ($this->ContactPerson as $cp) {
+			$cp->toXML($e);
+		}
+
+		foreach ($this->AdditionalMetadataLocation as $n) {
+			$n->toXML($e);
+		}
+
+		$this->signElement($e, $e->firstChild);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/Extensions.php b/lib/SAML2/XML/md/Extensions.php
new file mode 100644
index 0000000000000000000000000000000000000000..2f5e9ae915bfe967ec8c5f32ed760220e3da0fcf
--- /dev/null
+++ b/lib/SAML2/XML/md/Extensions.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Class for handling SAML2 metadata extensions.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_Extensions {
+
+	/**
+	 * Get a list of Extensions in the given element.
+	 *
+	 * @param DOMElement $parent  The element that may contain the md:Extensions element.
+	 * @return array  Array of extensions.
+	 */
+	public static function getList(DOMElement $parent) {
+
+		$ret = array();
+		foreach (SAML2_Utils::xpQuery($parent, './saml_metadata:Extensions/*') as $node) {
+			if ($node->namespaceURI === SAML2_XML_shibmd_Scope::NS && $node->localName === 'Scope') {
+				$ret[] = new SAML2_XML_shibmd_Scope($node);
+			} elseif ($node->namespaceURI === SAML2_XML_mdattr_EntityAttributes::NS && $node->localName === 'EntityAttributes') {
+				$ret[] = new SAML2_XML_mdattr_EntityAttributes($node);
+			} else {
+				$ret[] = new SAML2_XML_Chunk($node);
+			}
+		}
+
+		return $ret;
+	}
+
+
+	/**
+	 * Add a list of Extensions to the given element.
+	 *
+	 * @param DOMElement $parent  The element we should add the extensions to.
+	 * @param array $extensions  List of extension objects.
+	 */
+	public static function addList(DOMElement $parent, array $extensions) {
+
+		if (empty($extensions)) {
+			return;
+		}
+
+		$extElement = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, 'md:Extensions');
+		$parent->appendChild($extElement);
+
+		foreach ($extensions as $ext) {
+			$ext->toXML($extElement);
+		}
+	}
+
+}
diff --git a/lib/SAML2/XML/md/IDPSSODescriptor.php b/lib/SAML2/XML/md/IDPSSODescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..67116ced880851378fa257bf891bf01dfb9cc20a
--- /dev/null
+++ b/lib/SAML2/XML/md/IDPSSODescriptor.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * Class representing SAML 2 IDPSSODescriptor.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_IDPSSODescriptor extends SAML2_XML_md_SSODescriptorType {
+
+	/**
+	 * Whether AuthnRequests sent to this IdP should be signed.
+	 *
+	 * @var bool|NULL
+	 */
+	public $WantAuthnRequestsSigned = NULL;
+
+
+	/**
+	 * List of SingleSignOnService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $SingleSignOnService = array();
+
+
+	/**
+	 * List of NameIDMappingService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $NameIDMappingService = array();
+
+
+	/**
+	 * List of AssertionIDRequestService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AssertionIDRequestService = array();
+
+
+	/**
+	 * List of supported attribute profiles.
+	 *
+	 * Array with strings.
+	 *
+	 * @var array
+	 */
+	public $AttributeProfile = array();
+
+
+	/**
+	 * List of supported attributes.
+	 *
+	 * Array with SAML2_XML_saml_Attribute objects.
+	 *
+	 * @var array
+	 */
+	public $Attribute = array();
+
+
+	/**
+	 * Initialize an IDPSSODescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('md:IDPSSODescriptor', $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->WantAuthnRequestsSigned = SAML2_Utils::parseBoolean($xml, 'WantAuthnRequestsSigned', NULL);
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:SingleSignOnService') as $ep) {
+			$this->SingleSignOnService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:NameIDMappingService') as $ep) {
+			$this->NameIDMappingService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) {
+			$this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs);
+		}
+
+		$this->AttributeProfile = SAML2_Utils::extractStrings($xml, './saml_metadata:AttributeProfile');
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute') as $a) {
+			$this->Attribute[] = new SAML2_XML_saml_Attribute($a);
+		}
+	}
+
+
+	/**
+	 * Add this IDPSSODescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this IDPSSODescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_null($this->WantAuthnRequestsSigned) || is_bool($this->WantAuthnRequestsSigned)');
+		assert('is_array($this->SingleSignOnService)');
+		assert('is_array($this->NameIDMappingService)');
+		assert('is_array($this->AssertionIDRequestService)');
+		assert('is_array($this->AttributeProfile)');
+		assert('is_array($this->Attribute)');
+
+		$e = parent::toXML($parent);
+
+		if ($this->WantAuthnRequestsSigned === TRUE) {
+			$e->setAttribute('WantAuthnRequestsSigned', 'true');
+		} elseif ($this->WantAuthnRequestsSigned === FALSE) {
+			$e->setAttribute('WantAuthnRequestsSigned', 'false');
+		}
+
+		foreach ($this->SingleSignOnService as $ep) {
+			$ep->toXML($e, 'md:SingleSignOnService');
+		}
+
+		foreach ($this->NameIDMappingService as $ep) {
+			$ep->toXML($e, 'md:NameIDMappingService');
+		}
+
+		foreach ($this->AssertionIDRequestService as $ep) {
+			$ep->toXML($e, 'md:AssertionIDRequestService');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:AttributeProfile', FALSE, $this->AttributeProfile);
+
+		foreach ($this->Attribute as $a) {
+			$a->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/IndexedEndpointType.php b/lib/SAML2/XML/md/IndexedEndpointType.php
new file mode 100644
index 0000000000000000000000000000000000000000..c0191525c945d2bcf935cbbad1747a31b48101ab
--- /dev/null
+++ b/lib/SAML2/XML/md/IndexedEndpointType.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * Class representing SAML 2 IndexedEndpointType.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_IndexedEndpointType extends SAML2_XML_md_EndpointType {
+
+	/**
+	 * The index for this endpoint.
+	 *
+	 * @var int
+	 */
+	public $index;
+
+
+	/**
+	 * Whether this endpoint is the default.
+	 *
+	 * @var bool|NULL
+	 */
+	public $isDefault = NULL;
+
+
+	/**
+	 * Initialize an IndexedEndpointType.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct($xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('index')) {
+			throw new Exception('Missing index on ' . $xml->tagName);
+		}
+		$this->index = (int)$xml->getAttribute('index');
+
+		$this->isDefault = SAML2_Utils::parseBoolean($xml, 'isDefault', NULL);
+	}
+
+
+	/**
+	 * Add this endpoint to an XML element.
+	 *
+	 * @param DOMElement $parent  The element we should append this endpoint to.
+	 * @param string $name  The name of the element we should create.
+	 */
+	public function toXML(DOMElement $parent, $name) {
+		assert('is_string($name)');
+		assert('is_int($this->index)');
+		assert('is_null($this->isDefault) || is_bool($this->isDefault)');
+
+		$e = parent::toXML($parent, $name);
+		$e->setAttribute('index', (string)$this->index);
+
+		if ($this->isDefault === TRUE) {
+			$e->setAttribute('isDefault', 'true');
+		} elseif ($this->isDefault === FALSE) {
+			$e->setAttribute('isDefault', 'false');
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/KeyDescriptor.php b/lib/SAML2/XML/md/KeyDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..aeaffe9a53cc424fd19c24cf475f6fa9c010a39d
--- /dev/null
+++ b/lib/SAML2/XML/md/KeyDescriptor.php
@@ -0,0 +1,97 @@
+<?php
+
+/**
+ * Class representing a KeyDescriptor element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_KeyDescriptor {
+
+	/**
+	 * What this key can be used for.
+	 *
+	 * 'encryption', 'signing' or NULL.
+	 *
+	 * @var string|NULL
+	 */
+	public $use;
+
+
+	/**
+	 * The KeyInfo for this key.
+	 *
+	 * @var SAML2_XML_ds_KeyInfo
+	 */
+	public $KeyInfo;
+
+
+	/**
+	 * Supported EncryptionMethods.
+	 *
+	 * Array of SAML2_XML_Chunk objects.
+	 *
+	 * @var array
+	 */
+	public $EncryptionMethod = array();
+
+
+	/**
+	 * Initialize an KeyDescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if ($xml->hasAttribute('use')) {
+			$this->use = $xml->getAttribute('use');
+		}
+
+		$keyInfo = SAML2_Utils::xpQuery($xml, './ds:KeyInfo');
+		if (count($keyInfo) > 1) {
+			throw new Exception('More than one ds:KeyInfo in the KeyDescriptor.');
+		} elseif (empty($keyInfo)) {
+			throw new Exception('No ds:KeyInfo in the KeyDescriptor.');
+		}
+		$this->KeyInfo = new SAML2_XML_ds_KeyInfo($keyInfo[0]);
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:EncryptionMethod') as $em) {
+			$this->EncryptionMethod[] = new SAML2_XML_Chunk($em);
+		}
+
+	}
+
+
+	/**
+	 * Convert this KeyDescriptor to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this KeyDescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_null($this->use) || is_string($this->use)');
+		assert('$this->KeyInfo instanceof SAML2_XML_ds_KeyInfo');
+		assert('is_array($this->EncryptionMethod)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:KeyDescriptor');
+		$parent->appendChild($e);
+
+		if (isset($this->use)) {
+			$e->setAttribute('use', $this->use);
+		}
+
+		$this->KeyInfo->toXML($e);
+
+		foreach ($this->EncryptionMethod as $em) {
+			$em->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/Organization.php b/lib/SAML2/XML/md/Organization.php
new file mode 100644
index 0000000000000000000000000000000000000000..3869b042ae5ae7f04116e3a164d3f1ac51ed209f
--- /dev/null
+++ b/lib/SAML2/XML/md/Organization.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * Class representing SAML 2 Organization element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_Organization {
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * The OrganizationName, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $OrganizationName = array();
+
+
+	/**
+	 * The OrganizationDisplayName, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $OrganizationDisplayName = array();
+
+
+	/**
+	 * The OrganizationURL, as an array of language => translation.
+	 *
+	 * @var array
+	 */
+	public $OrganizationURL = array();
+
+
+	/**
+	 * Initialize an Organization element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+
+		$this->OrganizationName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationName');
+		if (empty($this->OrganizationName)) {
+			$this->OrganizationName = array('invalid' => '');
+		}
+
+		$this->OrganizationDisplayName = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationDisplayName');
+		if (empty($this->OrganizationDisplayName)) {
+			$this->OrganizationDisplayName = array('invalid' => '');
+		}
+
+		$this->OrganizationURL = SAML2_Utils::extractLocalizedStrings($xml, './saml_metadata:OrganizationURL');
+		if (empty($this->OrganizationURL)) {
+			$this->OrganizationURL = array('invalid' => '');
+		}
+	}
+
+
+	/**
+	 * Convert this Organization to XML.
+	 *
+	 * @param DOMElement $parent  The element we should add this organization to.
+	 * @return DOMElement  This Organization-element.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->Extensions)');
+		assert('is_array($this->OrganizationName)');
+		assert('!empty($this->OrganizationName)');
+		assert('is_array($this->OrganizationDisplayName)');
+		assert('!empty($this->OrganizationDisplayName)');
+		assert('is_array($this->OrganizationURL)');
+		assert('!empty($this->OrganizationURL)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_Const::NS_MD, 'md:Organization');
+		$parent->appendChild($e);
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationName', TRUE, $this->OrganizationName);
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationDisplayName', TRUE, $this->OrganizationDisplayName);
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:OrganizationURL', TRUE, $this->OrganizationURL);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/PDPDescriptor.php b/lib/SAML2/XML/md/PDPDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..e2765fdea3f76e0bbf2e9e2a7e788c8cab1fe8aa
--- /dev/null
+++ b/lib/SAML2/XML/md/PDPDescriptor.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Class representing SAML 2 metadata PDPDescriptor.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_PDPDescriptor extends SAML2_XML_md_RoleDescriptor {
+
+	/**
+	 * List of AuthzService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AuthzService = array();
+
+
+	/**
+	 * List of AssertionIDRequestService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AssertionIDRequestService = array();
+
+
+	/**
+	 * List of supported NameID formats.
+	 *
+	 * Array of strings.
+	 *
+	 * @var array
+	 */
+	public $NameIDFormat = array();
+
+
+	/**
+	 * Initialize an IDPSSODescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('md:PDPDescriptor', $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AuthzService') as $ep) {
+			$this->AuthzService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+		if (empty($this->AuthzService)) {
+			throw new Exception('Must have at least one AuthzService in PDPDescriptor.');
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionIDRequestService') as $ep) {
+			$this->AssertionIDRequestService[] = new SAML2_XML_md_EndpointType($airs);
+		}
+
+		$this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat');
+	}
+
+
+	/**
+	 * Add this PDPDescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this IDPSSODescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->AuthzService)');
+		assert('!empty($this->AuthzService)');
+		assert('is_array($this->AssertionIDRequestService)');
+		assert('is_array($this->NameIDFormat)');
+
+		$e = parent::toXML($parent);
+
+		foreach ($this->AuthzService as $ep) {
+			$ep->toXML($e, 'md:AuthzService');
+		}
+
+		foreach ($this->AssertionIDRequestService as $ep) {
+			$ep->toXML($e, 'md:AssertionIDRequestService');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/RequestedAttribute.php b/lib/SAML2/XML/md/RequestedAttribute.php
new file mode 100644
index 0000000000000000000000000000000000000000..124a25daa2139633865241a4d6f9116dc54462e8
--- /dev/null
+++ b/lib/SAML2/XML/md/RequestedAttribute.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Class representing SAML 2 metadata RequestedAttribute.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_RequestedAttribute extends SAML2_XML_saml_Attribute {
+
+	/**
+	 * Whether this attribute is required.
+	 *
+	 * @var bool|NULL
+	 */
+	public $isRequired = NULL;
+
+
+	/**
+	 * Initialize an RequestedAttribute.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct($xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->isRequired = SAML2_Utils::parseBoolean($xml, 'isRequired', NULL);
+	}
+
+
+	/**
+	 * Convert this RequestedAttribute to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this RequestedAttribute to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_bool($this->isRequired) || is_null($this->isRequired)');
+
+		$e = $this->toXMLInternal($parent, SAML2_Const::NS_MD, 'md:RequestedAttribute');
+
+		if ($this->isRequired === TRUE) {
+			$e->setAttribute('isRequired', 'true');
+		} elseif ($this->isRequired === FALSE) {
+			$e->setAttribute('isRequired', 'false');
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/RoleDescriptor.php b/lib/SAML2/XML/md/RoleDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..346d34cc1376419d54f29c1c775dc212e24b93d0
--- /dev/null
+++ b/lib/SAML2/XML/md/RoleDescriptor.php
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * Class representing SAML 2 RoleDescriptor element.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_RoleDescriptor extends SAML2_SignedElementHelper {
+
+	/**
+	 * The name of this descriptor element.
+	 *
+	 * @var string
+	 */
+	private $elementName;
+
+
+	/**
+	 * The ID of this element.
+	 *
+	 * @var string|NULL
+	 */
+	public $ID;
+
+
+	/**
+	 * How long this element is valid, as a unix timestamp.
+	 *
+	 * @var int|NULL
+	 */
+	public $validUntil;
+
+
+	/**
+	 * The length of time this element can be cached, as string.
+	 *
+	 * @var string|NULL
+	 */
+	public $cacheDuration;
+
+
+	/**
+	 * List of supported protocols.
+	 *
+	 * @var array
+	 */
+	public $protocolSupportEnumeration = array();
+
+
+	/**
+	 * Error URL for this role.
+	 *
+	 * @var string|NULL
+	 */
+	public $errorURL;
+
+
+	/**
+	 * Extensions on this element.
+	 *
+	 * Array of extension elements.
+	 *
+	 * @var array
+	 */
+	public $Extensions = array();
+
+
+	/**
+	 * KeyDescriptor elements.
+	 *
+	 * Array of SAML2_XML_md_KeyDescriptor elements.
+	 *
+	 * @var array
+	 */
+	public $KeyDescriptor = array();
+
+
+	/**
+	 * Organization of this role.
+	 *
+	 * @var SAML2_XML_md_Organization|NULL
+	 */
+	public $Organization = NULL;
+
+
+	/**
+	 * ContactPerson elements for this role.
+	 *
+	 * Array of SAML2_XML_md_ContactPerson objects.
+	 *
+	 * @var array
+	 */
+	public $ContactPerson = array();
+
+
+	/**
+	 * Initialize a RoleDescriptor.
+	 *
+	 * @param string $elementName  The name of this element.
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	protected function __construct($elementName, DOMElement $xml = NULL) {
+		assert('is_string($elementName)');
+
+		parent::__construct($xml);
+		$this->elementName = $elementName;
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if ($xml->hasAttribute('ID')) {
+			$this->ID = $xml->getAttribute('ID');
+		}
+		if ($xml->hasAttribute('validUntil')) {
+			$this->validUntil = SimpleSAML_Utilities::parseSAML2Time($xml->getAttribute('validUntil'));
+		}
+		if ($xml->hasAttribute('cacheDuration')) {
+			$this->cacheDuration = $xml->getAttribute('cacheDuration');
+		}
+
+		if (!$xml->hasAttribute('protocolSupportEnumeration')) {
+			throw new Exception('Missing protocolSupportEnumeration attribute on ' . $xml->localName);
+		}
+		$this->protocolSupportEnumeration = preg_split('/[\s]+/', $xml->getAttribute('protocolSupportEnumeration'));
+
+		if ($xml->hasAttribute('errorURL')) {
+			$this->errorURL = $xml->getAttribute('errorURL');
+		}
+
+
+		$this->Extensions = SAML2_XML_md_Extensions::getList($xml);
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:KeyDescriptor') as $kd) {
+			$this->KeyDescriptor[] = new SAML2_XML_md_KeyDescriptor($kd);
+		}
+
+		$organization = SAML2_Utils::xpQuery($xml, './saml_metadata:Organization');
+		if (count($organization) > 1) {
+			throw new Exception('More than one Organization in the entity.');
+		} elseif (!empty($organization)) {
+			$this->Organization = new SAML2_XML_md_Organization($organization[0]);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ContactPerson') as $cp) {
+			$this->contactPersons[] = new SAML2_XML_md_ContactPerson($cp);
+		}
+	}
+
+
+	/**
+	 * Add this RoleDescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this endpoint to.
+	 * @param string $name  The name of the element we should create.
+	 */
+	protected function toXML(DOMElement $parent) {
+		assert('is_null($this->ID) || is_string($this->ID)');
+		assert('is_null($this->validUntil) || is_int($this->validUntil)');
+		assert('is_null($this->cacheDuration) || is_string($this->cacheDuration)');
+		assert('is_array($this->protocolSupportEnumeration)');
+		assert('is_null($this->errorURL) || is_string($this->errorURL)');
+		assert('is_array($this->Extensions)');
+		assert('is_array($this->KeyDescriptor)');
+		assert('is_null($this->Organization) || $this->Organization instanceof SAML2_XML_md_Organization');
+		assert('is_array($this->ContactPerson)');
+
+		$e = $parent->ownerDocument->createElementNS(SAML2_Const::NS_MD, $this->elementName);
+		$parent->appendChild($e);
+
+		if (isset($this->ID)) {
+			$e->setAttribute('ID', $this->ID);
+		}
+
+		if (isset($this->validUntil)) {
+			$e->setAttribute('validUntil', gmdate('Y-m-d\TH:i:s\Z', $this->validUntil));
+		}
+
+		if (isset($this->cacheDuration)) {
+			$e->setAttribute('cacheDuration', $this->cacheDuration);
+		}
+
+		$e->setAttribute('protocolSupportEnumeration', implode(' ', $this->protocolSupportEnumeration));
+
+		if (isset($this->errorURL)) {
+			$e->setAttribute('errorURL', $this->errorURL);
+		}
+
+
+		SAML2_XML_md_Extensions::addList($e, $this->Extensions);
+
+		foreach ($this->KeyDescriptor as $kd) {
+			$kd->toXML($e);
+		}
+
+		if (isset($this->Organization)) {
+			$this->Organization->toXML($e);
+		}
+
+		foreach ($this->ContactPerson as $cp) {
+			$cp->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/SPSSODescriptor.php b/lib/SAML2/XML/md/SPSSODescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..da7077e0028d6ff82d31314867d72141c2a5f72b
--- /dev/null
+++ b/lib/SAML2/XML/md/SPSSODescriptor.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * Class representing SAML 2 SPSSODescriptor.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_SPSSODescriptor extends SAML2_XML_md_SSODescriptorType {
+
+	/**
+	 * Whether this SP signs authentication requests.
+	 *
+	 * @var bool|NULL
+	 */
+	public $AuthnRequestsSigned = NULL;
+
+
+	/**
+	 * Whether this SP wants the Assertion elements to be signed.
+	 *
+	 * @var bool|NULL
+	 */
+	public $WantAssertionsSigned = NULL;
+
+
+	/**
+	 * List of AssertionConsumerService endpoints for this SP.
+	 *
+	 * Array with IndexedEndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $AssertionConsumerService = array();
+
+
+	/**
+	 * List of AttributeConsumingService descriptors for this SP.
+	 *
+	 * Array with SAML2_XML_md_AttribteConsumingService objects.
+	 *
+	 * @var array
+	 */
+	public $AttributeConsumingService = array();
+
+
+	/**
+	 * Initialize a SPSSODescriptor.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+		parent::__construct('md:SPSSODescriptor', $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->AuthnRequestsSigned = SAML2_Utils::parseBoolean($xml, 'AuthnRequestsSigned', NULL);
+		$this->WantAssertionsSigned = SAML2_Utils::parseBoolean($xml, 'WantAssertionsSigned', NULL);
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AssertionConsumerService') as $ep) {
+			$this->AssertionConsumerService[] = new SAML2_XML_md_IndexedEndpointType($ep);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:AttributeConsumingService') as $acs) {
+			$this->AttributeConsumingService[] = new SAML2_XML_md_AttributeConsumingService($acs);
+		}
+	}
+
+
+	/**
+	 * Add this SPSSODescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this SPSSODescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_null($this->AuthnRequestsSigned) || is_bool($this->AuthnRequestsSigned)');
+		assert('is_null($this->WantAssertionsSigned) || is_bool($this->WantAssertionsSigned)');
+		assert('is_array($this->AssertionConsumerService)');
+		assert('is_array($this->AttributeConsumingService)');
+
+		$e = parent::toXML($parent);
+
+		if ($this->AuthnRequestsSigned === TRUE) {
+			$e->setAttribute('AuthnRequestsSigned', 'true');
+		} elseif ($this->AuthnRequestsSigned === FALSE) {
+			$e->setAttribute('AuthnRequestsSigned', 'false');
+		}
+
+		if ($this->WantAssertionsSigned === TRUE) {
+			$e->setAttribute('WantAssertionsSigned', 'true');
+		} elseif ($this->WantAssertionsSigned === FALSE) {
+			$e->setAttribute('WantAssertionsSigned', 'false');
+		}
+
+
+		foreach ($this->AssertionConsumerService as $ep) {
+			$ep->toXML($e, 'md:AssertionConsumerService');
+		}
+
+		foreach ($this->AttributeConsumingService as $acs) {
+			$acs->toXML($e);
+		}
+	}
+
+}
diff --git a/lib/SAML2/XML/md/SSODescriptorType.php b/lib/SAML2/XML/md/SSODescriptorType.php
new file mode 100644
index 0000000000000000000000000000000000000000..4ba5f5a5777076098b2dd53f57d9da33bc348766
--- /dev/null
+++ b/lib/SAML2/XML/md/SSODescriptorType.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * Class representing SAML 2 SSODescriptorType.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+abstract class SAML2_XML_md_SSODescriptorType extends SAML2_XML_md_RoleDescriptor {
+
+	/**
+	 * List of ArtifactResolutionService endpoints.
+	 *
+	 * Array with IndexedEndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $ArtifactResolutionService = array();
+
+
+	/**
+	 * List of SingleLogoutService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $SingleLogoutService = array();
+
+
+	/**
+	 * List of ManageNameIDService endpoints.
+	 *
+	 * Array with EndpointType objects.
+	 *
+	 * @var array
+	 */
+	public $ManageNameIDService = array();
+
+
+	/**
+	 * List of supported NameID formats.
+	 *
+	 * Array of strings.
+	 *
+	 * @var array
+	 */
+	public $NameIDFormat = array();
+
+
+	/**
+	 * Initialize a SSODescriptor.
+	 *
+	 * @param string $elementName  The name of this element.
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	protected function __construct($elementName, DOMElement $xml = NULL) {
+		assert('is_string($elementName)');
+
+		parent::__construct($elementName, $xml);
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ArtifactResolutionService') as $ep) {
+			$this->ArtifactResolutionService[] = new SAML2_XML_md_IndexedEndpointType($ep);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:SingleLogoutService') as $ep) {
+			$this->SingleLogoutService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_metadata:ManageNameIDService') as $ep) {
+			$this->ManageNameIDService[] = new SAML2_XML_md_EndpointType($ep);
+		}
+
+		$this->NameIDFormat = SAML2_Utils::extractStrings($xml, './saml_metadata:NameIDFormat');
+	}
+
+
+	/**
+	 * Add this SSODescriptorType to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this SSODescriptorType to.
+	 * @param string $name  The name of the element we should create.
+	 * @return DOMElement  The generated SSODescriptor DOMElement.
+	 */
+	protected function toXML(DOMElement $parent) {
+		assert('is_array($this->ArtifactResolutionService)');
+		assert('is_array($this->SingleLogoutService)');
+		assert('is_array($this->ManageNameIDService)');
+		assert('is_array($this->NameIDFormat)');
+
+		$e = parent::toXML($parent);
+
+		foreach ($this->ArtifactResolutionService as $ep) {
+			$ep->toXML($e, 'md:ArtifactResolutionService');
+		}
+
+		foreach ($this->SingleLogoutService as $ep) {
+			$ep->toXML($e, 'md:SingleLogoutService');
+		}
+
+		foreach ($this->ManageNameIDService as $ep) {
+			$ep->toXML($e, 'md:ManageNameIDService');
+		}
+
+		SAML2_Utils::addStrings($e, SAML2_Const::NS_MD, 'md:NameIDFormat', FALSE, $this->NameIDFormat);
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/md/UnknownRoleDescriptor.php b/lib/SAML2/XML/md/UnknownRoleDescriptor.php
new file mode 100644
index 0000000000000000000000000000000000000000..66e3a7986a643a46ca52afbe9dfcb44b820ec529
--- /dev/null
+++ b/lib/SAML2/XML/md/UnknownRoleDescriptor.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * Class representing unknown RoleDescriptors.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_md_UnknownRoleDescriptor extends SAML2_XML_md_RoleDescriptor {
+
+	/**
+	 * This RoleDescriptor as XML
+	 *
+	 * @var SAML2_XML_Chunk
+	 */
+	private $xml;
+
+
+	/**
+	 * Initialize an unknown RoleDescriptor.
+	 *
+	 * @param DOMElement $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml) {
+		parent::__construct('md:RoleDescriptor', $xml);
+
+		$this->xml = new SAML2_XML_Chunk($xml);
+	}
+
+
+	/**
+	 * Add this RoleDescriptor to an EntityDescriptor.
+	 *
+	 * @param DOMElement $parent  The EntityDescriptor we should append this RoleDescriptor to.
+	 */
+	public function toXML(DOMElement $parent) {
+
+		$this->xml->toXML($parent);
+	}
+
+}
diff --git a/lib/SAML2/XML/mdattr/EntityAttributes.php b/lib/SAML2/XML/mdattr/EntityAttributes.php
new file mode 100644
index 0000000000000000000000000000000000000000..a53056917631589c1c74c6425bbd1a509c79ced5
--- /dev/null
+++ b/lib/SAML2/XML/mdattr/EntityAttributes.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Class for handling the EntityAttributes metadata extension.
+ *
+ * @link: http://docs.oasis-open.org/security/saml/Post2.0/sstc-metadata-attr-cs-01.pdf
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_mdattr_EntityAttributes {
+
+	/**
+	 * The namespace used for the EntityAttributes extension.
+	 */
+	const NS = 'urn:oasis:names:tc:SAML:metadata:attribute';
+
+
+	/**
+	 * Array with child elements.
+	 *
+	 * The elements can be SAML2_XML_saml_Attribute or SAML2_XML_Chunk elements.
+	 *
+	 * @var array
+	 */
+	public $children;
+
+
+	/**
+	 * Create a EntityAttributes element.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:Attribute|./saml_assertion:Assertion') as $node) {
+			if ($node->localName === 'Attribute') {
+				$this->children[] = new SAML2_XML_saml_Attribute($node);
+			} else {
+				$this->children[] = new SAML2_XML_Chunk($node);
+			}
+		}
+
+	}
+
+
+	/**
+	 * Convert this EntityAttributes to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_array($this->children)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_mdattr_EntityAttributes::NS, 'mdattr:EntityAttributes');
+		$parent->appendChild($e);
+
+		foreach ($this->children as $child) {
+			$child->toXML($e);
+		}
+
+		return $e;
+	}
+
+}
diff --git a/lib/SAML2/XML/saml/Attribute.php b/lib/SAML2/XML/saml/Attribute.php
new file mode 100644
index 0000000000000000000000000000000000000000..afbf0cbbe5f5c22a84813e6f53497604e31bc845
--- /dev/null
+++ b/lib/SAML2/XML/saml/Attribute.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * Class representing SAML 2 Attribute.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_saml_Attribute {
+
+	/**
+	 * The Name of this attribute.
+	 *
+	 * @var string
+	 */
+	public $Name;
+
+
+	/**
+	 * The NameFormat of this attribute.
+	 *
+	 * @var string|NULL
+	 */
+	public $NameFormat;
+
+
+	/**
+	 * The FriendlyName of this attribute.
+	 *
+	 * @var string|NULL
+	 */
+	public $FriendlyName = NULL;
+
+
+	/**
+	 * List of attribute values.
+	 *
+	 * Array of SAML2_XML_saml_AttributeValue elements.
+	 *
+	 * @var array
+	 */
+	public $AttributeValue = array();
+
+
+	/**
+	 * Initialize an Attribute.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		if (!$xml->hasAttribute('Name')) {
+			throw new Exception('Missing Name on Attribute.');
+		}
+		$this->Name = $xml->getAttribute('Name');
+
+		if ($xml->hasAttribute('NameFormat')) {
+			$this->NameFormat = $xml->getAttribute('NameFormat');
+		}
+
+		if ($xml->hasAttribute('FriendlyName')) {
+			$this->FriendlyName = $xml->getAttribute('FriendlyName');
+		}
+
+		foreach (SAML2_Utils::xpQuery($xml, './saml_assertion:AttributeValue') as $av) {
+			$this->AttributeValue[] = new SAML2_XML_saml_AttributeValue($av);
+		}
+	}
+
+
+	/**
+	 * Internal implementation of toXML.
+	 * This function allows RequestedAttribute to specify the element name and namespace.
+	 *
+	 * @param DOMElement $parent  The element we should append this Attribute to.
+	 * @param string $namespace  The namespace the element should be created in.
+	 * @param string $name  The name of the element.
+	 */
+	protected function toXMLInternal(DOMElement $parent, $namespace, $name) {
+		assert('is_string($namespace)');
+		assert('is_string($name)');
+		assert('is_string($this->Name)');
+		assert('is_null($this->NameFormat) || is_string($this->NameFormat)');
+		assert('is_null($this->FriendlyName) || is_string($this->FriendlyName)');
+		assert('is_array($this->AttributeValue)');
+
+		$e = $parent->ownerDocument->createElementNS($namespace, $name);
+		$parent->appendChild($e);
+
+		$e->setAttribute('Name', $this->Name);
+
+		if (isset($this->NameFormat)) {
+			$e->setAttribute('NameFormat', $this->NameFormat);
+		}
+
+		if (isset($this->FriendlyName)) {
+			$e->setAttribute('FriendlyName', $this->FriendlyName);
+		}
+
+		foreach ($this->AttributeValue as $av) {
+			$av->toXML($e);
+		}
+
+		return $e;
+	}
+
+
+	/**
+	 * Convert this Attribute to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Attribute to.
+	 */
+	public function toXML(DOMElement $parent) {
+		return $this->toXMLInternal($parent, SAML2_Const::NS_SAML, 'saml:Attribute');
+	}
+
+}
diff --git a/lib/SAML2/XML/saml/AttributeValue.php b/lib/SAML2/XML/saml/AttributeValue.php
new file mode 100644
index 0000000000000000000000000000000000000000..5351e999af43f344802669a59561f3316f9719dd
--- /dev/null
+++ b/lib/SAML2/XML/saml/AttributeValue.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * Class representing an AttributeValue.
+ *
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_saml_AttributeValue {
+
+	/**
+	 * The raw DOMElement representing this value.
+	 *
+	 * @var DOMElement
+	 */
+	public $element;
+
+
+	/**
+	 * Create an AttributeValue.
+	 *
+	 * @param mixed $value
+	 * The value of this element.
+	 * Can be one of:
+	 *  - string                      Create an attribute value with a simple string.
+	 *  - DOMElement(AttributeValue)  Create an attribute value of the given DOMElement.
+	 *  - DOMElement                  Create an attribute value with the given DOMElement as a child.
+	 */
+	public function __construct($value) {
+		assert('is_string($value) || $value instanceof DOMElement');
+
+		if (is_string($value)) {
+			$doc = new DOMDocument();
+			$this->element = $doc->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeValue');
+			$this->element->setAttributeNS(SAML2_Const::NS_XSI, 'xsi:type', 'xs:string');
+			$this->element->appendChild($doc->createTextNode($value));
+
+			/* Make sure that the xs-namespace is available in the AttributeValue (for xs:string). */
+			$this->element->setAttributeNS(SAML2_Const::NS_XS, 'xs:tmp', 'tmp');
+			$this->element->removeAttributeNS(SAML2_Const::NS_XS, 'tmp');
+
+			return;
+		}
+
+		if ($value->namespaceURI === SAML2_Const::NS_SAML && $value->localName === 'AttributeValue') {
+			$this->element = SAML2_Utils::copyElement($value);
+			return;
+		}
+
+		$doc = new DOMDocument();
+		$this->element = $doc->createElementNS(SAML2_Const::NS_SAML, 'saml:AttributeValue');
+		SAML2_Utils::copyElement($value, $this->element);
+	}
+
+
+	/**
+	 * Append this attribute value to an element.
+	 *
+	 * @param DOMElement $parent  The element we should append this attribute value to.
+	 * @return DOMElement  The generated AttributeValue element.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('$this->element instanceof DOMElement');
+		assert('$this->element->namespaceURI === SAML2_Const::NS_SAML && $this->element->localName === "AttributeValue"');
+
+		$v = SAML2_Utils::copyElement($this->element, $parent);
+
+		return $v;
+	}
+
+
+	/**
+	 * Convert this attribute value to a string.
+	 *
+	 * If this element contains XML data, that data vil be encoded as a string and returned.
+	 *
+	 * @return string  This attribute value.
+	 */
+	public function __toString() {
+		assert('$this->element instanceof DOMElement');
+
+		$doc = $this->element->ownerDocument;
+
+		$ret = '';
+		foreach ($this->element->childNodes as $c) {
+			$ret .= $doc->saveXML($c);
+		}
+
+		return $ret;
+	}
+
+}
diff --git a/lib/SAML2/XML/shibmd/Scope.php b/lib/SAML2/XML/shibmd/Scope.php
new file mode 100644
index 0000000000000000000000000000000000000000..f095ba3fe0d07d628d67c4d9145b3b61e5f2bc3f
--- /dev/null
+++ b/lib/SAML2/XML/shibmd/Scope.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Class which represents the Scope element found in Shibboleth metadata.
+ *
+ * @link https://spaces.internet2.edu/display/SHIB/ShibbolethMetadataProfile
+ * @package simpleSAMLphp
+ * @version $Id$
+ */
+class SAML2_XML_shibmd_Scope {
+
+	/**
+	 * The namespace used for the Scope extension element.
+	 */
+	const NS = 'urn:mace:shibboleth:metadata:1.0';
+
+
+	/**
+	 * The scope.
+	 *
+	 * @var string
+	 */
+	public $scope;
+
+	/**
+	 * Whether this is a regexp scope.
+	 *
+	 * @var bool|NULL
+	 */
+	public $regexp = NULL;
+
+
+	/**
+	 * Create a Scope.
+	 *
+	 * @param DOMElement|NULL $xml  The XML element we should load.
+	 */
+	public function __construct(DOMElement $xml = NULL) {
+
+		if ($xml === NULL) {
+			return;
+		}
+
+		$this->scope = $xml->textContent;
+		$this->regexp = SAML2_Utils::parseBoolean($xml, 'regexp', NULL);
+	}
+
+
+	/**
+	 * Convert this Scope to XML.
+	 *
+	 * @param DOMElement $parent  The element we should append this Scope to.
+	 */
+	public function toXML(DOMElement $parent) {
+		assert('is_string($this->scope)');
+		assert('is_bool($this->regexp) || is_null($this->regexp)');
+
+		$doc = $parent->ownerDocument;
+
+		$e = $doc->createElementNS(SAML2_XML_shibmd_Scope::NS, 'shibmd:Scope');
+		$parent->appendChild($e);
+
+		$e->appendChild($doc->createTextNode($this->scope));
+
+		if ($this->regexp === TRUE) {
+			$e->setAttribute('regexp', 'true');
+		} elseif ($this->regexp === FALSE) {
+			$e->setAttribute('regexp', 'false');
+		}
+
+		return $e;
+	}
+
+}