diff --git a/setup/simplesamlphp/saml2/src/SAML2/XML/md/ContactPerson.php b/setup/simplesamlphp/saml2/src/SAML2/XML/md/ContactPerson.php new file mode 100644 index 0000000..582cb2e --- /dev/null +++ b/setup/simplesamlphp/saml2/src/SAML2/XML/md/ContactPerson.php @@ -0,0 +1,436 @@ +hasAttribute('contactType')) { + throw new \Exception('Missing contactType on ContactPerson.'); + } + $this->setContactType($xml->getAttribute('contactType')); + + $this->setExtensions(Extensions::getList($xml)); + + $this->setCompany(self::getStringElement($xml, 'Company')); + $this->setGivenName(self::getStringElement($xml, 'GivenName')); + $this->setSurName(self::getStringElement($xml, 'SurName')); + $this->setEmailAddress(self::getStringElements($xml, 'EmailAddress')); + $this->setTelephoneNumber(self::getStringElements($xml, 'TelephoneNumber')); + + foreach ($xml->attributes as $attr) { + if ($attr->nodeName == "contactType") { + continue; + } + + $this->addContactPersonAttributes($attr->nodeName, $attr->nodeValue); + } + } + + + /** + * 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, string $name) : array + { + $e = Utils::xpQuery($parent, './saml_metadata:'.$name); + + $ret = []; + 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. + * @throws \Exception + * @return string|null The value of the child element. + */ + private static function getStringElement(\DOMElement $parent, string $name) : ?string + { + $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]; + } + + + /** + * Collect the value of the contactType-property + * + * @return string + */ + public function getContactType() : string + { + return $this->contactType; + } + + + /** + * Set the value of the contactType-property + * + * @param string $contactType + * @return void + */ + public function setContactType(string $contactType) : void + { + $this->contactType = $contactType; + } + + + /** + * Collect the value of the Company-property + * + * @return string|null + */ + public function getCompany() : ?string + { + return $this->Company; + } + + + /** + * Set the value of the Company-property + * + * @param string|null $company + * @return void + */ + public function setCompany(string $company = null) : void + { + $this->Company = $company; + } + + + /** + * Collect the value of the GivenName-property + * + * @return string|null + */ + public function getGivenName() : ?string + { + return $this->GivenName; + } + + + /** + * Set the value of the GivenName-property + * + * @param string|null $givenName + * @return void + */ + public function setGivenName(string $givenName = null) : void + { + $this->GivenName = $givenName; + } + + + /** + * Collect the value of the SurName-property + * + * @return string|null + */ + public function getSurName() : ?string + { + return $this->SurName; + } + + + /** + * Set the value of the SurName-property + * + * @param string|null $surName + * @return void + */ + public function setSurName(string $surName = null) : void + { + $this->SurName = $surName; + } + + + /** + * Collect the value of the EmailAddress-property + * + * @return string[] + */ + public function getEmailAddress() : array + { + return $this->EmailAddress; + } + + + /** + * Set the value of the EmailAddress-property + * + * @param string[] $emailAddress + * @return void + */ + public function setEmailAddress(array $emailAddress) : void + { + $emailAddress = preg_replace('/^mailto:/i', '', $emailAddress); + $this->EmailAddress = $emailAddress; + } + + + /** + * Add the value to the EmailAddress-property + * + * @param string $emailAddress + * @return void + */ + public function addEmailAddress($emailAddress) : void + { + $emailAddress = preg_replace('/^mailto:/i', '', $emailAddress); + $this->EmailAddress[] = $emailAddress; + } + + + /** + * Collect the value of the TelephoneNumber-property + * + * @return string[] + */ + public function getTelephoneNumber() : array + { + return $this->TelephoneNumber; + } + + + /** + * Set the value of the TelephoneNumber-property + * + * @param string[] $telephoneNumber + * @return void + */ + public function setTelephoneNumber(array $telephoneNumber) : void + { + $this->TelephoneNumber = $telephoneNumber; + } + + + /** + * Add the value to the TelephoneNumber-property + * + * @param string $telephoneNumber + * @return void + */ + public function addTelephoneNumber($telephoneNumber) : void + { + $this->TelephoneNumber[] = $telephoneNumber; + } + + + /** + * Collect the value of the Extensions-property + * + * @return \SAML2\XML\Chunk[] + */ + public function getExtensions() : array + { + return $this->Extensions; + } + + + /** + * Set the value of the Extensions-property + * + * @param array $extensions + * @return void + */ + public function setExtensions(array $extensions) : void + { + $this->Extensions = $extensions; + } + + + /** + * Add an Extension. + * + * @param \SAML2\XML\Chunk $extensions The Extensions + * @return void + */ + public function addExtension(Chunk $extension) : void + { + $this->Extensions[] = $extension; + } + + + /** + * Collect the value of the ContactPersonAttributes-property + * + * @return string[] + */ + public function getContactPersonAttributes() : array + { + return $this->ContactPersonAttributes; + } + + + /** + * Set the value of the ContactPersonAttributes-property + * + * @param string[] $contactPersonAttributes + * @return void + */ + public function setContactPersonAttributes(array $contactPersonAttributes) : void + { + $this->ContactPersonAttributes = $contactPersonAttributes; + } + + + /** + * Add the key/value of the ContactPersonAttributes-property + * + * @param string $attr + * @param string $value + * @return void + */ + public function addContactPersonAttributes(string $attr, string $value) : void + { + $this->ContactPersonAttributes[$attr] = $value; + } + + + /** + * 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) : DOMElement + { + $doc = $parent->ownerDocument; + + $e = $doc->createElementNS(Constants::NS_MD, 'md:ContactPerson'); + $parent->appendChild($e); + + $e->setAttribute('contactType', $this->getContactType()); + + foreach ($this->getContactPersonAttributes() as $attr => $val) { + $e->setAttribute($attr, $val); + } + + Extensions::addList($e, $this->getExtensions()); + + if ($this->Company !== null) { + Utils::addString($e, Constants::NS_MD, 'md:Company', $this->Company); + } + if ($this->GivenName !== null) { + Utils::addString($e, Constants::NS_MD, 'md:GivenName', $this->GivenName); + } + if ($this->SurName !== null) { + Utils::addString($e, Constants::NS_MD, 'md:SurName', $this->SurName); + } + if (!empty($this->getEmailAddress())) { + /** @var array $addresses */ + + /* SPID CUSTOM - remove mailto: from metadata */ + //$addresses = preg_filter('/^/', 'mailto:', $this->EmailAddress); + $addresses = $this->EmailAddress; + + Utils::addStrings($e, Constants::NS_MD, 'md:EmailAddress', false, $addresses); + } + if (!empty($this->getTelephoneNumber())) { + Utils::addStrings($e, Constants::NS_MD, 'md:TelephoneNumber', false, $this->getTelephoneNumber()); + } + + return $e; + } +} diff --git a/setup/simplesamlphp/simplesamlphp/lib/SimpleSAML/Metadata/Signer.php b/setup/simplesamlphp/simplesamlphp/lib/SimpleSAML/Metadata/Signer.php new file mode 100644 index 0000000..0c7883b --- /dev/null +++ b/setup/simplesamlphp/simplesamlphp/lib/SimpleSAML/Metadata/Signer.php @@ -0,0 +1,304 @@ + $entityMetadata['metadata.sign.privatekey'], + 'certificate' => $entityMetadata['metadata.sign.certificate'] + ]; + + if (array_key_exists('metadata.sign.privatekey_pass', $entityMetadata)) { + $ret['privatekey_pass'] = $entityMetadata['metadata.sign.privatekey_pass']; + } + + return $ret; + } + + // then we look for default values in the global configuration + $privatekey = $config->getString('metadata.sign.privatekey', null); + $certificate = $config->getString('metadata.sign.certificate', null); + if ($privatekey !== null || $certificate !== null) { + if ($privatekey === null || $certificate === null) { + throw new \Exception( + 'Missing either the "metadata.sign.privatekey" or the' . + ' "metadata.sign.certificate" configuration option in the global' . + ' configuration. If one of these options is specified, then the other' . + ' must also be specified.' + ); + } + $ret = ['privatekey' => $privatekey, 'certificate' => $certificate]; + + $privatekey_pass = $config->getString('metadata.sign.privatekey_pass', null); + if ($privatekey_pass !== null) { + $ret['privatekey_pass'] = $privatekey_pass; + } + + return $ret; + } + + // as a last resort we attempt to use the privatekey and certificate option from the metadata + if ( + array_key_exists('privatekey', $entityMetadata) + || array_key_exists('certificate', $entityMetadata) + ) { + if ( + !array_key_exists('privatekey', $entityMetadata) + || !array_key_exists('certificate', $entityMetadata) + ) { + throw new \Exception( + 'Both the "privatekey" and the "certificate" option must' . + ' be set in the metadata for the ' . $type . ' "' . + $entityMetadata['entityid'] . '" before it is possible to sign metadata' . + ' from this entity.' + ); + } + + $ret = [ + 'privatekey' => $entityMetadata['privatekey'], + 'certificate' => $entityMetadata['certificate'] + ]; + + if (array_key_exists('privatekey_pass', $entityMetadata)) { + $ret['privatekey_pass'] = $entityMetadata['privatekey_pass']; + } + + return $ret; + } + + throw new \Exception( + 'Could not find what key & certificate should be used to sign the metadata' . + ' for the ' . $type . ' "' . $entityMetadata['entityid'] . '".' + ); + } + + + /** + * Determine whether metadata signing is enabled for the given metadata. + * + * @param \SimpleSAML\Configuration $config Our \SimpleSAML\Configuration instance. + * @param array $entityMetadata The metadata of the entity. + * @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or + * 'Shib 1.3 SP'. + * + * @return boolean True if metadata signing is enabled, false otherwise. + * @throws \Exception If the value of the 'metadata.sign.enable' option is not a boolean. + */ + private static function isMetadataSigningEnabled(Configuration $config, array $entityMetadata, string $type): bool + { + // first check the metadata for the entity + if (array_key_exists('metadata.sign.enable', $entityMetadata)) { + if (!is_bool($entityMetadata['metadata.sign.enable'])) { + throw new \Exception( + 'Invalid value for the "metadata.sign.enable" configuration option for' . + ' the ' . $type . ' "' . $entityMetadata['entityid'] . '". This option' . + ' should be a boolean.' + ); + } + + return $entityMetadata['metadata.sign.enable']; + } + + $enabled = $config->getBoolean('metadata.sign.enable', false); + + return $enabled; + } + + + /** + * Determine the signature and digest algorithms to use when signing metadata. + * + * This method will look for the 'metadata.sign.algorithm' key in the $entityMetadata array, or look for such + * a configuration option in the $config object. + * + * @param \SimpleSAML\Configuration $config The global configuration. + * @param array $entityMetadata An array containing the metadata related to this entity. + * @param string $type A string describing the type of entity. E.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. + * + * @return array An array with two keys, 'algorithm' and 'digest', corresponding to the signature and digest + * algorithms to use, respectively. + * + * @throws \SimpleSAML\Error\CriticalConfigurationError + */ + private static function getMetadataSigningAlgorithm( + Configuration $config, + array $entityMetadata, + string $type + ): array { + // configure the algorithm to use + if (array_key_exists('metadata.sign.algorithm', $entityMetadata)) { + if (!is_string($entityMetadata['metadata.sign.algorithm'])) { + throw new Error\CriticalConfigurationError( + "Invalid value for the 'metadata.sign.algorithm' configuration option for the " . $type . + "'" . $entityMetadata['entityid'] . "'. This option has restricted values" + ); + } + $alg = $entityMetadata['metadata.sign.algorithm']; + } else { + $alg = $config->getString('metadata.sign.algorithm', XMLSecurityKey::RSA_SHA256); + } + + $supported_algs = [ + XMLSecurityKey::RSA_SHA1, + XMLSecurityKey::RSA_SHA256, + XMLSecurityKey::RSA_SHA384, + XMLSecurityKey::RSA_SHA512, + ]; + + if (!in_array($alg, $supported_algs, true)) { + throw new Error\CriticalConfigurationError("Unknown signature algorithm '$alg'"); + } + + switch ($alg) { + case XMLSecurityKey::RSA_SHA256: + $digest = XMLSecurityDSig::SHA256; + break; + case XMLSecurityKey::RSA_SHA384: + $digest = XMLSecurityDSig::SHA384; + break; + case XMLSecurityKey::RSA_SHA512: + $digest = XMLSecurityDSig::SHA512; + break; + default: + $digest = XMLSecurityDSig::SHA1; + } + + return [ + 'algorithm' => $alg, + 'digest' => $digest, + ]; + } + + + /** + * Signs the given metadata if metadata signing is enabled. + * + * @param string $metadataString A string with the metadata. + * @param array $entityMetadata The metadata of the entity. + * @param string $type A string which describes the type entity this is, e.g. 'SAML 2 IdP' or 'Shib 1.3 SP'. + * + * @return string The $metadataString with the signature embedded. + * @throws \Exception If the certificate or private key cannot be loaded, or the metadata doesn't parse properly. + */ + public static function sign($metadataString, $entityMetadata, $type) + { + $config = Configuration::getInstance(); + + // check if metadata signing is enabled + if (!self::isMetadataSigningEnabled($config, $entityMetadata, $type)) { + return $metadataString; + } + + // find the key & certificate which should be used to sign the metadata + $keyCertFiles = self::findKeyCert($config, $entityMetadata, $type); + + $keyFile = Utils\Config::getCertPath($keyCertFiles['privatekey']); + if (!file_exists($keyFile)) { + throw new \Exception( + 'Could not find private key file [' . $keyFile . '], which is needed to sign the metadata' + ); + } + $keyData = file_get_contents($keyFile); + + $certFile = Utils\Config::getCertPath($keyCertFiles['certificate']); + if (!file_exists($certFile)) { + throw new \Exception( + 'Could not find certificate file [' . $certFile . '], which is needed to sign the metadata' + ); + } + $certData = file_get_contents($certFile); + + + // convert the metadata to a DOM tree + try { + $xml = DOMDocumentFactory::fromString($metadataString); + } catch (\Exception $e) { + throw new \Exception('Error parsing self-generated metadata.'); + } + + $signature_cf = self::getMetadataSigningAlgorithm($config, $entityMetadata, $type); + + // load the private key + $objKey = new XMLSecurityKey($signature_cf['algorithm'], ['type' => 'private']); + if (array_key_exists('privatekey_pass', $keyCertFiles)) { + $objKey->passphrase = $keyCertFiles['privatekey_pass']; + } + $objKey->loadKey($keyData, false); + + // get the EntityDescriptor node we should sign + /** @var \DOMElement $rootNode */ + $rootNode = $xml->firstChild; + $rootNode->setAttribute('ID', 'spid-php_' . hash('sha256', $metadataString)); + + // sign the metadata with our private key + $objXMLSecDSig = new XMLSecurityDSig(); + + $objXMLSecDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); + + $objXMLSecDSig->addReferenceList( + [$rootNode], + $signature_cf['digest'], + ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N], + ['id_name' => 'ID', 'overwrite' => false] + ); + + $objXMLSecDSig->sign($objKey); + + // add the certificate to the signature + $objXMLSecDSig->add509Cert($certData, true); + + // add the signature to the metadata + $objXMLSecDSig->insertSignature($rootNode, $rootNode->firstChild); + + // return the DOM tree as a string + return $xml->saveXML(); + } +}