diff --git a/.coveralls.yml b/.coveralls.yml
index 173ff356..cb6cdeb2 100644
--- a/.coveralls.yml
+++ b/.coveralls.yml
@@ -1,4 +1,4 @@
-service_name: travis-ci
+service_name: github
src_dir: lib
diff --git a/.github/workflows/php-package.yml b/.github/workflows/php-package.yml
index ea1b48bb..4f12009f 100644
--- a/.github/workflows/php-package.yml
+++ b/.github/workflows/php-package.yml
@@ -5,9 +5,9 @@ name: php-saml package
on:
push:
- branches: [ master, 2.* ]
+ branches: [ master, 3.*, 4.* ]
pull_request:
- branches: [ master, 2.* ]
+ branches: [ master, 3.*, 4.* ]
jobs:
test:
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f3c0a43e..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-language: php
-dist: trusty
-php:
- - 5.5
- - 5.6
-
-matrix:
- fast_finish: true
- include:
- - php: 5.3
- dist: precise
- - php: 5.4
- dist: precise
-
-env:
- - TRAVIS=true
-
-before_install:
- - composer self-update || true
- - composer install --prefer-source --no-interaction
-
-before_script:
- - phpenv config-rm xdebug.ini
-
-script:
- - vendor/bin/phpunit
- - php vendor/bin/phpcpd --exclude tests --exclude vendor .
- - php vendor/bin/phploc . --exclude vendor
- - php vendor/bin/phploc lib/.
- - mkdir -p tests/build/dependences
- - php vendor/bin/pdepend --summary-xml=tests/build/logs/dependence-summary.xml --jdepend-chart=tests/build/dependences/jdepend.svg --overview-pyramid=tests/build/dependences/pyramid.svg lib/.
- - php vendor/bin/phpcs --standard=tests/ZendModStandard lib/Saml2 demo1 demo2 demo-old endpoints tests/src
-
-after_script:
- - export TRAVIS=https://travis-ci.org/onelogin/php-saml
- - echo $TRAVIS
- - echo $TRAVIS_JOB_ID
- - php vendor/bin/coveralls --config .coveralls.yml -v
diff --git a/CHANGELOG b/CHANGELOG
index 08a1a53a..8ddb5764 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,34 @@
CHANGELOG
=========
+
+v.2.21.0
+* [#619](https://github.com/SAML-Toolkits/php-saml/pull/619) Add Parameter checking on validateBinarySign, inspired on CVE-2025-27773
+* [#603](https://github.com/SAML-Toolkits/php-saml/issues/603) Fix typo in ignoreValidUntil that breaks metadata. Add parameter to exclude validUntil on Settings getSPMetadata
+* [#594](https://github.com/SAML-Toolkits/php-saml/pull/594) Add support for encrypted name id in encrypted assertion
+* Fix buildWithBaseURLPath
+* Doc fix typo
+* Remove Travis CI references
+
+v.2.20.0
+* [#586](https://github.com/SAML-Toolkits/php-saml/pull/586) IdPMetadataParser::parseRemoteXML - Add argument for setting whether to validate peer SSL certificate
+* [#585](https://github.com/SAML-Toolkits/php-saml/pull/585) Declare conditional return types
+* Make Saml2\Auth can accept a param $spValidationOnly
+* [#577](https://github.com/SAML-Toolkits/php-saml/pull/577) Allow empty NameID value when no strict or wantNameId is false
+* [#570](https://github.com/SAML-Toolkits/php-saml/pull/570) Support X509 cert comments
+* [#569](https://github.com/SAML-Toolkits/php-saml/pull/569) Add parameter to exclude validUntil on SP Metadata XML
+* [#551](https://github.com/SAML-Toolkits/php-saml/pull/551) Fix compatibility with proxies that extends HTTP_X_FORWARDED_HOST
+* [#487](https://github.com/SAML-Toolkits/php-saml/issues/487) Enable strict check on in_array method
+* Fix typos on readme.
+* [#480](https://github.com/SAML-Toolkits/php-saml/pull/480) Fix typo on SPNameQualifier mismatch error message
+* Add $spValidationOnly param to Auth
+* Update xmlseclibs (3.1.2 without AES-GCM and OAEP support)
+* Add warning about Open Redirect and Reply attacks
+* Add warning about the use of IdpMetadataParser class. If Metadata URLs
+ are provided by 3rd parties, the URL inputs MUST be validated to avoid issues like SSRF
+* Update dependencies
+* Fix test payloads
+* Remove references to OneLogin.
+
v.2.19.1
* [#467](https://github.com/onelogin/php-saml/issues/467) Fix bug on getSelfRoutedURLNoQuery method
@@ -8,7 +37,7 @@ v.2.19.0
* [#433](https://github.com/onelogin/php-saml/issues/443) Fix Incorrect Destination in LogoutResponse when using responseUrl #443
* Add support for SMARTCARD_PKI and RSA_TOKEN Auth Contexts
* Support Statements with Attribute elements with the same name enabling the allowRepeatAttributeName setting
-* Get lib path dinamically
+* Get lib path dynamically
* Check for x509Cert of the IdP when loading settings, even if the security index was not provided
v.2.18.1
@@ -31,7 +60,7 @@ v.2.17.1
v.2.17.0
* Set true as the default value for strict setting
* Support 'x509cert' and 'privateKey' on signMetadata security settings
-* Relax comparision of false on SignMetadata
+* Relax comparison of false on SignMetadata
* Fix CI
v.2.16.0
@@ -70,7 +99,7 @@ v.2.12.0
* [#263](https://github.com/onelogin/php-saml/issues/263) Fix incompatibility with ADFS on SLO. When on php saml settings NameID Format is set as unspecified but the SAMLResponse has no NameID Format, no NameID Format should be specified on LogoutRequest.
v.2.11.0
-* [#236](https://github.com/onelogin/php-saml/pull/236) Exclude unnecesary files from Composer production downloads
+* [#236](https://github.com/onelogin/php-saml/pull/236) Exclude unnecessary files from Composer production downloads
* [#226](https://github.com/onelogin/php-saml/pull/226) Add possibility to handle nameId NameQualifier attribute in SLO Request
* Improve logout documentation on Readme.
* Improve multi-certificate support
@@ -176,14 +205,14 @@ v.2.7.0
* Fix PHP 7 error (used continue outside a loop/switch).
* Fix bug on organization element of the SP metadata builder.
* Fix typos on documentation. Fix ALOWED Misspell.
-* Be able to extract RequestID. Add RequestID validation on demo1.
+* Be able to extract RequestID. Add RequestID validation on demo1.
* Add $stay parameter to login, logout and processSLO method.
v.2.6.1
-------
* Fix bug on cacheDuration of the Metadata XML generated.
* Make SPNameQualifier optional on the generateNameId method. Avoid the use of SPNameQualifier when generating the NameID on the LogoutRequest builder.
-* Allows the authn comparsion attribute to be set via config.
+* Allows the authn comparison attribute to be set via config.
* Retrieve Session Timeout after processResponse with getSessionExpiration().
* Improve readme readability.
* Allow single log out to work for applications not leveraging php session_start. Added a callback parameter in order to close the session at processSLO.
@@ -201,8 +230,8 @@ v.2.6.0
v.2.5.0
-------
-* Do accesible the ID of the object Logout Request (id attribute).
-* Add note about the fact that PHP 5.3 is unssuported.
+* Do accessible the ID of the object Logout Request (id attribute).
+* Add note about the fact that PHP 5.3 is unsupported.
* Add fingerprint algorithm support.
* Add dependences to composer.
@@ -230,7 +259,7 @@ v.2.2.0
-------
* Fix bug with Encrypted nameID on LogoutRequest.
* Fixed usability bug. SP will inform about AuthFail status after process a Response.
-* Added SessionIndex support on LogoutRequest, and know is accesible from the Auth class.
+* Added SessionIndex support on LogoutRequest, and know is accessible from the Auth class.
* LogoutRequest and LogoutResponse classes now accept non deflated xml.
* Improved the XML metadata/ Decrypted Assertion output. (prettyprint).
* Fix bug in formatPrivateKey method, the key could be not RSA.
diff --git a/README.md b/README.md
index d7b6cf12..8c6407af 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,9 @@
Add SAML support to your PHP software using this library.
-**The 3.X branch is compatible with PHP > 7.1, so if you are using that PHP version, use it and not the 2.X or the master branch**
+**The 3.X branch is compatible with PHP 7.0, PHP 7.1, PHP 7.2 , so if you are using that PHP version, use it and not the 2.X or the master branch**
+
+**The 4.X branch is compatible with PHP >= 7.3 and PHP 8.X**
Warning
-------
@@ -189,14 +191,14 @@ a trusted and expected URL.
Read more about Open Redirect [CWE-601](https://cwe.mitre.org/data/definitions/601.html).
-### Avoiding Reply attacks ###
+### Avoiding Replay attacks ###
-A reply attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
+A replay attack is basically try to reuse an intercepted valid SAML Message in order to impersonate a SAML action (SSO or SLO).
SAML Messages have a limited timelife (NotBefore, NotOnOrAfter) that
make harder this kind of attacks, but they are still possible.
-In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs alredy valdidated and processed. Those values only need
+In order to avoid them, the SP can keep a list of SAML Messages or Assertion IDs already validated and processed. Those values only need
to be stored the amount of time of the SAML Message life time, so
we don't need to store all processed message/assertion Ids, but the most recent ones.
@@ -554,11 +556,11 @@ $advancedSettings = array (
// If true, Destination URL should strictly match to the address to
// which the response has been sent.
- // Notice that if 'relaxDestinationValidation' is true an empty Destintation
+ // Notice that if 'relaxDestinationValidation' is true an empty Destination
// will be accepted.
'destinationStrictlyMatches' => false,
- // If true, SAMLResponses with an InResponseTo value will be rejectd if not
+ // If true, SAMLResponses with an InResponseTo value will be rejected if not
// AuthNRequest ID provided to the validation method.
'rejectUnsolicitedResponsesWithInResponseTo' => false,
@@ -598,7 +600,7 @@ $advancedSettings = array (
),
// Organization information template, the info in en_US lang is
- // recomended, add more if required.
+ // recommended, add more if required.
'organization' => array (
'en-US' => array(
'name' => '',
@@ -713,7 +715,7 @@ The login method can receive other six optional parameters:
* `$parameters` - An array of parameters that will be added to the `GET` in the HTTP-Redirect.
* `$forceAuthn` - When true the `AuthNRequest` will set the `ForceAuthn='true'`
* `$isPassive` - When true the `AuthNRequest` will set the `Ispassive='true'`
-* `$strict` - True if we want to stay (returns the url string) False to redirect
+* `$stay` - True if we want to stay (returns the url string) False to redirect
* `$setNameIdPolicy` - When true the AuthNRequest will set a nameIdPolicy element.
* `$nameIdValueReq` - Indicates to the IdP the subject that should be authenticated.
@@ -945,7 +947,7 @@ $auth->processSLO(false, $requestID);
$errors = $auth->getErrors();
if (empty($errors)) {
- echo 'Sucessfully logged out';
+ echo 'Successfully logged out';
} else {
echo implode(', ', $errors);
}
@@ -1152,7 +1154,7 @@ if (isset($_GET['sso'])) { // SSO action. Will send an AuthNRequest to the I
echo '
', implode(', ', $errors), '
';
}
// This check if the response was
- if (!$auth->isAuthenticated()) { // sucessfully validated and the user
+ if (!$auth->isAuthenticated()) { // successfully validated and the user
echo "Not authenticated
"; // data retrieved or not
exit();
}
@@ -1167,7 +1169,7 @@ if (isset($_GET['sso'])) { // SSO action. Will send an AuthNRequest to the I
$auth->processSLO(); // Process the Logout Request & Logout Response
$errors = $auth->getErrors(); // Retrieves possible validation errors
if (empty($errors)) {
- echo 'Sucessfully logged out
';
+ echo 'Successfully logged out
';
} else {
echo '', implode(', ', $errors), '
';
}
@@ -1417,7 +1419,7 @@ SAML 2 Authentication Response class
SAML 2 Logout Request class
* `OneLogin_Saml2_LogoutRequest` - Constructs the Logout Request object.
- * `getRequest` - Returns the Logout Request defated, base64encoded, unsigned
+ * `getRequest` - Returns the Logout Request deflated, base64encoded, unsigned
* `getID` - Returns the ID of the Logout Request. (If you have the object you can access to the id attribute)
* `getNameIdData` - Gets the NameID Data of the the Logout Request.
* `getNameId` - Gets the NameID of the Logout Request.
@@ -1484,7 +1486,7 @@ A class that contains functionality related to the metadata of the SP
* `builder` - Generates the metadata of the SP based on the settings.
* `signmetadata` - Signs the metadata with the key/cert provided
-* `addX509KeyDescriptors` - Adds the x509 descriptors (sign/encriptation) to
+* `addX509KeyDescriptors` - Adds the x509 descriptors (sign/encryption) to
the metadata
##### OneLogin_Saml2_Utils - `Utils.php` #####
diff --git a/advanced_settings_example.php b/advanced_settings_example.php
index 95b2f87e..ce974e19 100644
--- a/advanced_settings_example.php
+++ b/advanced_settings_example.php
@@ -91,11 +91,11 @@
// If true, Destination URL should strictly match to the address to
// which the response has been sent.
- // Notice that if 'relaxDestinationValidation' is true an empty Destintation
+ // Notice that if 'relaxDestinationValidation' is true an empty Destination
// will be accepted.
'destinationStrictlyMatches' => false,
- // If true, SAMLResponses with an InResponseTo value will be rejectd if not
+ // If true, SAMLResponses with an InResponseTo value will be rejected if not
// AuthNRequest ID provided to the validation method.
'rejectUnsolicitedResponsesWithInResponseTo' => false,
@@ -121,7 +121,7 @@
'lowercaseUrlencoding' => false,
),
- // Contact information template, it is recommended to suply a technical and support contacts
+ // Contact information template, it is recommended to supply a technical and support contacts
'contactPerson' => array (
'technical' => array (
'givenName' => '',
@@ -133,7 +133,7 @@
),
),
- // Organization information template, the info in en_US lang is recomended, add more if required
+ // Organization information template, the info in en_US lang is recommended, add more if required
'organization' => array (
'en-US' => array(
'name' => '',
diff --git a/demo1/Readme.txt b/demo1/Readme.txt
index d8810676..392ae176 100644
--- a/demo1/Readme.txt
+++ b/demo1/Readme.txt
@@ -43,7 +43,7 @@ How it works
notice that a RelayState parameter is set to the url that initiated the
process, the index.php view.
- 2.2 in the second link we access to (attrs.php) have the same process described at 2.1 with the diference that as RelayState is set the attrs.php
+ 2.2 in the second link we access to (attrs.php) have the same process described at 2.1 with the difference that as RelayState is set the attrs.php
3. The SAML Response is processed in the ACS (index.php?acs), if the Response
is not valid, the process stop here and a message is showed. Otherwise we
@@ -64,7 +64,7 @@ How it works
side, the logout process is initiated at the idP, sends a Logout Request to the SP (SLS endpoint, index.php?sls). The SLS endpoint of the SP
process the Logout Request and if is valid, close the session of the user
at the local app and send a Logout Response to the IdP (to the SLS endpoint
- of the IdP). The IdP recieve the Logout Response, process it and close the
+ of the IdP). The IdP receive the Logout Response, process it and close the
session at of the IdP. Notice that the SLO Workflow starts and ends at the IdP.
Notice that all the SAML Requests and Responses are handler at a unique file,
diff --git a/demo1/index.php b/demo1/index.php
index 8e1babd9..4ec511f3 100644
--- a/demo1/index.php
+++ b/demo1/index.php
@@ -53,7 +53,7 @@
$auth->logout($returnTo, $parameters, $nameId, $sessionIndex, false, $nameIdFormat, $samlNameIdNameQualifier, $samlNameIdSPNameQualifier);
# If LogoutRequest ID need to be saved in order to later validate it, do instead
- # $sloBuiltUrl = $auth->logout(null, $paramters, $nameId, $sessionIndex, true);
+ # $sloBuiltUrl = $auth->logout(null, $parameters, $nameId, $sessionIndex, true);
# $_SESSION['LogoutRequestID'] = $auth->getLastRequestID();
# header('Pragma: no-cache');
# header('Cache-Control: no-cache, must-revalidate');
@@ -105,7 +105,7 @@
$auth->processSLO(false, $requestID);
$errors = $auth->getErrors();
if (empty($errors)) {
- echo 'Sucessfully logged out
';
+ echo 'Successfully logged out
';
} else {
echo '', htmlentities(implode(', ', $errors)), '
';
if ($auth->getSettings()->isDebugActive()) {
diff --git a/demo2/Readme.txt b/demo2/Readme.txt
index 7a34800f..1be1ab01 100644
--- a/demo2/Readme.txt
+++ b/demo2/Readme.txt
@@ -54,7 +54,7 @@ demo1, only changes the targets.
3. We are logged in the app and the user attributes are showed.
At this point, we can test the single log out functionality.
- 4. The single log out funcionality could be tested by 2 ways.
+ 4. The single log out functionality could be tested by 2 ways.
4.1 SLO Initiated by SP. Click on the "logout" link at the SP, after that
we are redirected to the slo.php view and there a Logout Request is sent
@@ -69,7 +69,7 @@ demo1, only changes the targets.
Request to the SP (SLS endpoint sls.php of the endpoint folder).
The SLS endpoint of the SP process the Logout Request and if is valid,
close the session of the user at the local app and sends a Logout Response
- to the IdP (to the SLS endpoint of the IdP).The IdP recieves the Logout
+ to the IdP (to the SLS endpoint of the IdP).The IdP receives the Logout
Response, process it and close the session at of the IdP. Notice that the
SLO Workflow starts and ends at the IdP.
diff --git a/docs/Saml2/files/Settings.php.txt b/docs/Saml2/files/Settings.php.txt
index ba715b5e..66e5e83b 100644
--- a/docs/Saml2/files/Settings.php.txt
+++ b/docs/Saml2/files/Settings.php.txt
@@ -684,7 +684,7 @@ class OneLogin_Saml2_Settings
|| !isset($organization['displayname']) || empty($organization['displayname'])
|| !isset($organization['url']) || empty($organization['url'])
) {
- $errors[] = 'organization_not_enought_data';
+ $errors[] = 'organization_not_enough_data';
break;
}
}
diff --git a/endpoints/sls.php b/endpoints/sls.php
index 7dd508ba..909376e3 100644
--- a/endpoints/sls.php
+++ b/endpoints/sls.php
@@ -14,7 +14,7 @@
$errors = $auth->getErrors();
if (empty($errors)) {
- echo 'Sucessfully logged out';
+ echo 'Successfully logged out';
} else {
echo htmlentities(implode(', ', $errors));
}
diff --git a/extlib/xmlseclibs/xmlseclibs.php b/extlib/xmlseclibs/xmlseclibs.php
index d1095c8a..5139d62d 100644
--- a/extlib/xmlseclibs/xmlseclibs.php
+++ b/extlib/xmlseclibs/xmlseclibs.php
@@ -37,7 +37,7 @@
* @author Robert Richards
* @copyright 2007-2019 Robert Richards
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version 3.0.4 modified
+ * @version 3.1.2 modified
*/
class XMLSecurityKey {
@@ -198,13 +198,13 @@ public function getSymmetricKeySize() {
}
return $this->cryptParams['keysize'];
}
-
+
public function generateSessionKey() {
if (!isset($this->cryptParams['keysize'])) {
throw new Exception('Unknown key size for type "' . $this->type . '".');
}
$keysize = $this->cryptParams['keysize'];
-
+
if (function_exists('openssl_random_pseudo_bytes')) {
/* We have PHP >= 5.3 - use openssl to generate session key. */
$key = openssl_random_pseudo_bytes($keysize);
@@ -212,7 +212,7 @@ public function generateSessionKey() {
/* Generating random key using iv generation routines */
$key = mcrypt_create_iv($keysize, MCRYPT_RAND);
}
-
+
if ($this->type === XMLSecurityKey::TRIPLEDES_CBC) {
/* Make sure that the generated key has the proper parity bits set.
* Mcrypt doesn't care about the parity bits, but others may care.
@@ -227,7 +227,7 @@ public function generateSessionKey() {
$key[$i] = chr($byte);
}
}
-
+
$this->key = $key;
return $key;
}
@@ -281,6 +281,10 @@ public function loadKey($key, $isFile=false, $isCert = false) {
$this->key = openssl_get_publickey($this->key);
} else {
$this->key = openssl_get_privatekey($this->key, $this->passphrase);
+
+ if ($this->key === false) {
+ throw new Exception('Unable to extract private key (invalid key or passphrase): ' . openssl_error_string());
+ }
}
} else if (isset($this->cryptParams['cipher']) && $this->cryptParams['cipher'] == MCRYPT_RIJNDAEL_128) {
/* Check key length */
@@ -469,7 +473,7 @@ static function convertRSA($modulus, $exponent) {
public function serializeKey($parent) {
}
-
+
/**
@@ -557,7 +561,7 @@ public function __construct() {
private function resetXPathObj() {
$this->xPathCtx = null;
}
-
+
private function getXPathObj() {
if (empty($this->xPathCtx) && ! empty($this->sigNode)) {
$xpath = new DOMXPath($this->sigNode->ownerDocument);
@@ -654,7 +658,7 @@ private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefi
$withComments = true;
break;
}
-
+
if (is_null($arXPath) && ($node instanceof DOMNode) && ($node->ownerDocument !== null) && $node->isSameNode($node->ownerDocument->documentElement)) {
/* Check for any PI or comments as they would have been excluded */
$element = $node;
@@ -668,7 +672,7 @@ private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefi
$node = $node->ownerDocument;
}
}
-
+
return $node->C14N($exclusive, $withComments, $arXPath, $prefixList);
}
@@ -686,10 +690,22 @@ public function canonicalizeSignedInfo() {
if ($signInfoNode = $nodeset->item(0)) {
$query = "./secdsig:CanonicalizationMethod";
$nodeset = $xpath->query($query, $signInfoNode);
+ $prefixList = null;
if ($canonNode = $nodeset->item(0)) {
$canonicalmethod = $canonNode->getAttribute('Algorithm');
+ foreach ($canonNode->childNodes as $node)
+ {
+ if ($node->localName == 'InclusiveNamespaces') {
+ if ($pfx = $node->getAttribute('PrefixList')) {
+ $arpfx = array_filter(explode(' ', $pfx));
+ if (count($arpfx) > 0) {
+ $prefixList = array_merge($prefixList ? $prefixList : array(), $arpfx);
+ }
+ }
+ }
+ }
}
- $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod);
+ $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod, null, $prefixList);
return $this->signedInfo;
}
}
@@ -918,10 +934,10 @@ public function validateReference() {
if ($nodeset->length == 0) {
throw new Exception("Reference nodes not found");
}
-
+
/* Initialize/reset the list of validated nodes. */
$this->validatedNodes = array();
-
+
foreach ($nodeset AS $refNode) {
if (! $this->processRefNode($refNode)) {
/* Clear the list of validated nodes. */
@@ -976,8 +992,8 @@ private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=nul
foreach ($arTransforms AS $transform) {
$transNode = $this->createNewSignNode('Transform');
$transNodes->appendChild($transNode);
- if (is_array($transform) &&
- (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
+ if (is_array($transform) &&
+ (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116'])) &&
(! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']))) {
$transNode->setAttribute('Algorithm', 'http://www.w3.org/TR/1999/REC-xpath-19991116');
$XPathNode = $this->createNewSignNode('XPath', $transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['query']);
@@ -1134,7 +1150,7 @@ public function appendKey($objKey, $parent=null) {
*
* @param $node The node the signature element should be inserted into.
* @param $beforeNode The node the signature element should be located before.
- *
+ *
* @return DOMNode The signature element node
*/
public function insertSignature($node, $beforeNode = null) {
@@ -1196,9 +1212,9 @@ static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=fa
if (! $parentRef instanceof DOMElement) {
throw new Exception('Invalid parent Node parameter');
}
-
+
list($parentRef, $keyInfo) = self::auxKeyInfo($parentRef, $xpath);
-
+
// Add all certs if there are more than one
$certs = XMLSecurityDSig::staticGet509XCerts($cert, $isPEMFormat);
@@ -1217,7 +1233,7 @@ static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=fa
$subjectName = true;
}
}
-
+
// Attach all certificate nodes and any additional data
foreach ($certs as $X509Cert){
if ($issuerSerial || $subjectName) {
@@ -1236,7 +1252,7 @@ static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=fa
}
$subjectNameValue = implode(',', $parts);
} else {
- $subjectNameValue = $certData['issuer'];
+ $subjectNameValue = $certData['subject'];
}
$x509SubjectNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509SubjectName', $subjectNameValue);
$x509DataNode->appendChild($x509SubjectNode);
@@ -1251,17 +1267,17 @@ static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=fa
} else {
$issuerName = $certData['issuer'];
}
-
+
$x509IssuerNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509IssuerSerial');
$x509DataNode->appendChild($x509IssuerNode);
-
+
$x509Node = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509IssuerName', $issuerName);
$x509IssuerNode->appendChild($x509Node);
$x509Node = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509SerialNumber', $certData['serialNumber']);
$x509IssuerNode->appendChild($x509Node);
}
}
-
+
}
$x509CertNode = $baseDoc->createElementNS(XMLSecurityDSig::XMLDSIGNS, 'ds:X509Certificate', $X509Cert);
$x509DataNode->appendChild($x509CertNode);
@@ -1273,14 +1289,14 @@ public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null
self::staticAdd509Cert($this->sigNode, $cert, $isPEMFormat, $isURL, $xpath, $options);
}
}
-
+
/**
* This function appends a node to the KeyInfo.
*
* The KeyInfo element will be created if one does not exist in the document.
*
* @param DOMNode $node The node to append to the KeyInfo.
- *
+ *
* @return DOMNode The KeyInfo element node
*/
public function appendToKeyInfo($node) {
@@ -1289,12 +1305,12 @@ public function appendToKeyInfo($node) {
$xpath = $this->getXPathObj();
list($parentRef, $keyInfo) = self::auxKeyInfo($parentRef, $xpath);
-
+
$keyInfo->appendChild($node);
-
+
return $keyInfo;
}
-
+
static function auxKeyInfo($parentRef, $xpath=null)
{
$baseDoc = $parentRef->ownerDocument;
@@ -1302,7 +1318,7 @@ static function auxKeyInfo($parentRef, $xpath=null)
$xpath = new DOMXPath($parentRef->ownerDocument);
$xpath->registerNamespace('secdsig', XMLSecurityDSig::XMLDSIGNS);
}
-
+
$query = "./secdsig:KeyInfo";
$nodeset = $xpath->query($query, $parentRef);
$keyInfo = $nodeset->item(0);
@@ -1507,7 +1523,7 @@ public function getCipherValue() {
* @params XMLSecurityKey $objKey The decryption key that should be used when decrypting the node.
* @params boolean $replace Whether we should replace the encrypted node in the XML document with the decrypted data. The default is true.
* @return string|DOMElement The decrypted data.
- */
+ */
public function decryptNode($objKey, $replace=true) {
if (! $objKey instanceof XMLSecurityKey) {
throw new Exception('Invalid Key');
@@ -1707,7 +1723,7 @@ static function staticLocateKeyInfo($objBaseKey=null, $node=null) {
if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) {
if ($x509certNodes->length > 0) {
$x509cert = $x509certNodes->item(0)->textContent;
- $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert);
+ $x509cert = str_replace(array("\r", "\n", " ", "\t"), "", $x509cert);
$x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
$objBaseKey->loadKey($x509cert, false, true);
}
diff --git a/lib/Saml2/Auth.php b/lib/Saml2/Auth.php
index c8a1c6f0..956c5052 100644
--- a/lib/Saml2/Auth.php
+++ b/lib/Saml2/Auth.php
@@ -143,8 +143,8 @@ class OneLogin_Saml2_Auth
/**
* Initializes the SP SAML instance.
*
- * @param array|object|null $oldSettings Setting data (You can provide a OneLogin_Saml_Settings, the settings object of the Saml folder implementation)
- * @param bool $spValidationOnly if you only as an SP , you should set it to false if not you should set it to true
+ * @param array|object|null $oldSettings Setting data (You can provide a OneLogin_Saml_Settings, the settings object of the Saml folder implementation)
+ * @param bool $spValidationOnly If true, The library will only validate the SAML SP settings
*
* @throws OneLogin_Saml2_Error
*/
@@ -246,6 +246,7 @@ public function processResponse($requestId = null)
* @param bool $stay True if we want to stay (returns the url string) False to redirect
*
* @return string|null
+ * @phpstan-return ($stay is true ? string : never)
*
* @throws OneLogin_Saml2_Error
*/
@@ -498,6 +499,7 @@ public function getAttributeWithFriendlyName($friendlyName)
* @param string $nameIdValueReq Indicates to the IdP the subject that should be authenticated
*
* @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
+ * @phpstan-return ($stay is true ? string : never)
*
* @throws OneLogin_Saml2_Error
*/
@@ -540,6 +542,7 @@ public function login($returnTo = null, $parameters = array(), $forceAuthn = fal
* @param string|null $nameIdNameQualifier The NameID NameQualifier will be set in the LogoutRequest.
*
* @return string|null If $stay is True, it return a string with the SLO URL + LogoutRequest + parameters
+ * @phpstan-return ($stay is true ? string : never)
*
* @throws OneLogin_Saml2_Error
*/
diff --git a/lib/Saml2/Error.php b/lib/Saml2/Error.php
index 7afc8ddd..ae0c2e55 100644
--- a/lib/Saml2/Error.php
+++ b/lib/Saml2/Error.php
@@ -25,6 +25,7 @@ class OneLogin_Saml2_Error extends Exception
const SAML_SINGLE_LOGOUT_NOT_SUPPORTED = 12;
const PRIVATE_KEY_NOT_FOUND = 13;
const UNSUPPORTED_SETTINGS_OBJECT = 14;
+ const INVALID_PARAMETER = 15;
/**
* Constructor
diff --git a/lib/Saml2/IdPMetadataParser.php b/lib/Saml2/IdPMetadataParser.php
index 195b5691..c2121ac1 100644
--- a/lib/Saml2/IdPMetadataParser.php
+++ b/lib/Saml2/IdPMetadataParser.php
@@ -25,7 +25,7 @@ class OneLogin_Saml2_IdPMetadataParser
*
* @return array metadata info in php-saml settings format
*/
- public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT)
+ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdFormat = null, $desiredSSOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $desiredSLOBinding = OneLogin_Saml2_Constants::BINDING_HTTP_REDIRECT, $validatePeer = false)
{
$metadataInfo = array();
@@ -37,7 +37,7 @@ public static function parseRemoteXML($url, $entityId = null, $desiredNameIdForm
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $validatePeer);
curl_setopt($ch, CURLOPT_FAILONERROR, 1);
$xml = curl_exec($ch);
diff --git a/lib/Saml2/LogoutRequest.php b/lib/Saml2/LogoutRequest.php
index 882a8daf..2a4a6a1e 100644
--- a/lib/Saml2/LogoutRequest.php
+++ b/lib/Saml2/LogoutRequest.php
@@ -136,7 +136,7 @@ public function __construct(OneLogin_Saml2_Settings $settings, $request = null,
/**
- * Returns the Logout Request defated, base64encoded, unsigned
+ * Returns the Logout Request deflated, base64encoded, unsigned
*
* @param bool|null $deflate Whether or not we should 'gzdeflate' the request body before we return it.
*
diff --git a/lib/Saml2/LogoutResponse.php b/lib/Saml2/LogoutResponse.php
index 21c1adad..763ee0ca 100644
--- a/lib/Saml2/LogoutResponse.php
+++ b/lib/Saml2/LogoutResponse.php
@@ -213,7 +213,7 @@ public function isValid($requestId = null, $retrieveParametersFromServer = false
}
/**
- * Extracts a node from the DOMDocument (Logout Response Menssage)
+ * Extracts a node from the DOMDocument (Logout Response Message)
*
* @param string $query Xpath Expresion
*
diff --git a/lib/Saml2/Metadata.php b/lib/Saml2/Metadata.php
index 9343ac44..3f63a093 100644
--- a/lib/Saml2/Metadata.php
+++ b/lib/Saml2/Metadata.php
@@ -21,10 +21,11 @@ class OneLogin_Saml2_Metadata
* @param array $contacts Contacts info
* @param array $organization Organization ingo
* @param array $attributes
+ * @param bool $ignoreValidUntil exclude the validUntil tag from metadata
*
* @return string SAML Metadata XML
*/
- public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array())
+ public static function builder($sp, $authnsign = false, $wsign = false, $validUntil = null, $cacheDuration = null, $contacts = array(), $organization = array(), $attributes = array(), $ignoreValidUntil = false)
{
if (!isset($validUntil)) {
@@ -144,27 +145,37 @@ public static function builder($sp, $authnsign = false, $wsign = false, $validUn
$requestedAttributeStr = implode(PHP_EOL, $requestedAttributeData);
$strAttributeConsumingService = <<
+
+
{$sp['attributeConsumingService']['serviceName']}
{$attrCsDesc}{$requestedAttributeStr}
METADATA_TEMPLATE;
}
+ if ($ignoreValidUntil) {
+ $timeStr = <<
{$sls} {$sp['NameIDFormat']}
- {$strAttributeConsumingService}
+ index="1" />{$strAttributeConsumingService}
{$strOrganization}{$strContacts}
METADATA_TEMPLATE;
diff --git a/lib/Saml2/Response.php b/lib/Saml2/Response.php
index 1b57400e..519af8e0 100644
--- a/lib/Saml2/Response.php
+++ b/lib/Saml2/Response.php
@@ -38,6 +38,13 @@ class OneLogin_Saml2_Response
*/
public $encrypted = false;
+ /**
+ * The response contains an encrypted nameId in the assertion.
+ *
+ * @var bool
+ */
+ public $encryptedNameId = false;
+
/**
* After validation, if it fail this var has the cause of the problem
* @var string
@@ -165,7 +172,7 @@ public function isValid($requestId = null)
}
$currentURL = OneLogin_Saml2_Utils::getSelfRoutedURLNoQuery();
-
+
$responseInResponseTo = null;
if ($this->document->documentElement->hasAttribute('InResponseTo')) {
$responseInResponseTo = $this->document->documentElement->getAttribute('InResponseTo');
@@ -200,14 +207,12 @@ public function isValid($requestId = null)
);
}
- if ($security['wantNameIdEncrypted']) {
- $encryptedIdNodes = $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData');
- if ($encryptedIdNodes->length != 1) {
- throw new OneLogin_Saml2_ValidationError(
- "The NameID of the Response is not encrypted and the SP requires it",
- OneLogin_Saml2_ValidationError::NO_ENCRYPTED_NAMEID
- );
- }
+ $this->encryptedNameId = $this->encryptedNameId || $this->_queryAssertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')->length > 0;
+ if (!$this->encryptedNameId && $security['wantNameIdEncrypted']) {
+ throw new OneLogin_Saml2_ValidationError(
+ "The NameID of the Response is not encrypted and the SP requires it",
+ OneLogin_Saml2_ValidationError::NO_ENCRYPTED_NAMEID
+ );
}
// Validate Conditions element exists
@@ -218,7 +223,7 @@ public function isValid($requestId = null)
);
}
- // Validate Asserion timestamps
+ // Validate Assertion timestamps
$this->validateTimestamps();
// Validate AuthnStatement element exists and is unique
@@ -357,7 +362,7 @@ public function isValid($requestId = null)
OneLogin_Saml2_ValidationError::NO_SIGNED_ASSERTION
);
}
-
+
if ($security['wantMessagesSigned'] && !$hasSignedResponse) {
throw new OneLogin_Saml2_ValidationError(
"The Message of the Response is not signed and the SP requires it",
@@ -366,17 +371,6 @@ public function isValid($requestId = null)
}
}
- // Detect case not supported
- if ($this->encrypted) {
- $encryptedIDNodes = OneLogin_Saml2_Utils::query($this->decryptedDocument, '/samlp:Response/saml:Assertion/saml:Subject/saml:EncryptedID');
- if ($encryptedIDNodes->length > 0) {
- throw new OneLogin_Saml2_ValidationError(
- 'SAML Response that contains a an encrypted Assertion with encrypted nameId is not supported.',
- OneLogin_Saml2_ValidationError::NOT_SUPPORTED
- );
- }
- }
-
if (empty($signedElements) || (!$hasSignedResponse && !$hasSignedAssertion)) {
throw new OneLogin_Saml2_ValidationError(
'No Signature found. SAML Response rejected',
@@ -585,9 +579,15 @@ public function getNameIdData()
if ($encryptedIdDataEntries->length == 1) {
$encryptedData = $encryptedIdDataEntries->item(0);
- $key = $this->_settings->getSPkey();
+ $pem = $this->_settings->getSPkey();
+ if (empty($pem)) {
+ throw new OneLogin_Saml2_Error(
+ "No private key available, check settings",
+ OneLogin_Saml2_Error::PRIVATE_KEY_NOT_FOUND
+ );
+ }
$seckey = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
- $seckey->loadKey($key);
+ $seckey->loadKey($pem);
$nameId = OneLogin_Saml2_Utils::decryptElement($encryptedData, $seckey);
@@ -600,8 +600,8 @@ public function getNameIdData()
$nameIdData = array();
+ $security = $this->_settings->getSecurityData();
if (!isset($nameId)) {
- $security = $this->_settings->getSecurityData();
if ($security['wantNameId']) {
throw new OneLogin_Saml2_ValidationError(
"NameID not found in the assertion of the Response",
@@ -609,7 +609,7 @@ public function getNameIdData()
);
}
} else {
- if ($this->_settings->isStrict() && empty($nameId->nodeValue)) {
+ if ($this->_settings->isStrict() && $security['wantNameId'] && empty($nameId->nodeValue)) {
throw new OneLogin_Saml2_ValidationError(
"An empty NameID value found",
OneLogin_Saml2_ValidationError::EMPTY_NAMEID
@@ -983,9 +983,9 @@ public function validateSignedElements($signedElements)
$responseTag = '{'.OneLogin_Saml2_Constants::NS_SAMLP.'}Response';
$assertionTag = '{'.OneLogin_Saml2_Constants::NS_SAML.'}Assertion';
- $ocurrence = array_count_values($signedElements);
- if ((in_array($responseTag, $signedElements) && $ocurrence[$responseTag] > 1) ||
- (in_array($assertionTag, $signedElements) && $ocurrence[$assertionTag] > 1) ||
+ $occurrence = array_count_values($signedElements);
+ if ((in_array($responseTag, $signedElements) && $occurrence[$responseTag] > 1) ||
+ (in_array($assertionTag, $signedElements) && $occurrence[$assertionTag] > 1) ||
!in_array($responseTag, $signedElements) && !in_array($assertionTag, $signedElements)
) {
return false;
@@ -1068,7 +1068,7 @@ protected function _queryAssertion($assertionXpath)
}
/**
- * Extracts nodes that match the query from the DOMDocument (Response Menssage)
+ * Extracts nodes that match the query from the DOMDocument (Response Message)
*
* @param string $query Xpath Expresion
*
@@ -1129,7 +1129,7 @@ protected function _decryptAssertion($dom)
$objKeyInfo->loadKey($pem, false, false);
}
}
-
+
if (empty($objKey->key)) {
$objKey->loadKey($key);
}
@@ -1139,6 +1139,17 @@ protected function _decryptAssertion($dom)
if ($check === false) {
throw new Exception('Error: string from decrypted assertion could not be loaded into a XML document');
}
+
+ // check if the decrypted assertion contains an encryptedID
+ $encryptedID = $decrypted->getElementsByTagName('EncryptedID')->item(0);
+ if ($encryptedID) {
+ // decrypt the encryptedID
+ $this->encryptedNameId = true;
+ $encryptedData = $encryptedID->getElementsByTagName('EncryptedData')->item(0);
+ $nameId = $this->decryptNameId($encryptedData, $pem);
+ OneLogin_Saml2_Utils::treeCopyReplace($encryptedID, $nameId);
+ }
+
if ($encData->parentNode instanceof DOMDocument) {
return $decrypted;
} else {
@@ -1171,6 +1182,46 @@ protected function _decryptAssertion($dom)
}
}
+ /**
+ * Decrypt EncryptedID element
+ *
+ * @param \DOMElement $encryptedData The encrypted data.
+ * @param string $key The private key
+ *
+ * @return \DOMElement The decrypted element.
+ *
+ * @throws OneLogin_Saml2_Error
+ * @throws OneLogin_Saml2_ValidationError
+ */
+ private function decryptNameId(\DOMElement $encryptedData, string $pem)
+ {
+ $objenc = new XMLSecEnc();
+ $encData = $objenc->locateEncryptedData($encryptedData);
+ $objenc->setNode($encData);
+ $objenc->type = $encData->getAttribute("Type");
+ if (!$objKey = $objenc->locateKey()) {
+ throw new OneLogin_Saml2_ValidationError(
+ "Unknown algorithm",
+ ValidationError::KEY_ALGORITHM_ERROR
+ );
+ }
+ $key = null;
+ if ($objKeyInfo = $objenc->locateKeyInfo($objKey)) {
+ if ($objKeyInfo->isEncrypted) {
+ $objencKey = $objKeyInfo->encryptedCtx;
+ $objKeyInfo->loadKey($pem, false, false);
+ $key = $objencKey->decryptKey($objKeyInfo);
+ } else {
+ // symmetric encryption key support
+ $objKeyInfo->loadKey($pem, false, false);
+ }
+ }
+ if (empty($objKey->key)) {
+ $objKey->loadKey($key);
+ }
+ return OneLogin_Saml2_Utils::decryptElement($encryptedData, $objKey);
+ }
+
/**
* After execute a validation process, if fails this method returns the cause
*
diff --git a/lib/Saml2/Settings.php b/lib/Saml2/Settings.php
index 358bf5ea..bec377e8 100644
--- a/lib/Saml2/Settings.php
+++ b/lib/Saml2/Settings.php
@@ -672,7 +672,7 @@ public function checkSPSettings($settings)
if (!isset($contact['givenName']) || empty($contact['givenName'])
|| !isset($contact['emailAddress']) || empty($contact['emailAddress'])
) {
- $errors[] = 'contact_not_enought_data';
+ $errors[] = 'contact_not_enough_data';
break;
}
}
@@ -684,7 +684,7 @@ public function checkSPSettings($settings)
|| !isset($organization['displayname']) || empty($organization['displayname'])
|| !isset($organization['url']) || empty($organization['url'])
) {
- $errors[] = 'organization_not_enought_data';
+ $errors[] = 'organization_not_enough_data';
break;
}
}
@@ -888,15 +888,16 @@ public function getIdPSLOResponseUrl()
* or $advancedSettings['security']['wantAssertionsEncrypted'] are enabled.
* @param DateTime|null $validUntil Metadata's valid time
* @param int|null $cacheDuration Duration of the cache in seconds
+ * @param bool $ignoreValidUntil exclude the validUntil tag from metadata
*
* @return string SP metadata (xml)
*
* @throws Exception
* @throws OneLogin_Saml2_Error
*/
- public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil = null, $cacheDuration = null)
+ public function getSPMetadata($alwaysPublishEncryptionCert = false, $validUntil = null, $cacheDuration = null, $ignoreValidUntil = false)
{
- $metadata = OneLogin_Saml2_Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization());
+ $metadata = OneLogin_Saml2_Metadata::builder($this->_sp, $this->_security['authnRequestsSigned'], $this->_security['wantAssertionsSigned'], $validUntil, $cacheDuration, $this->getContacts(), $this->getOrganization(), array(), $ignoreValidUntil);
$certNew = $this->getSPcertNew();
if (!empty($certNew)) {
@@ -1040,7 +1041,7 @@ public function formatIdPCert()
}
/**
- * Formats the Multple IdP certs.
+ * Formats the Multiple IdP certs.
*/
public function formatIdPCertMulti()
{
diff --git a/lib/Saml2/Utils.php b/lib/Saml2/Utils.php
index 24ecbd58..6ace614d 100644
--- a/lib/Saml2/Utils.php
+++ b/lib/Saml2/Utils.php
@@ -208,27 +208,29 @@ public static function treeCopyReplace(DomNode $targetNode, DomNode $sourceNode,
/**
* Returns a x509 cert (adding header & footer if required).
*
- * @param string $cert A x509 unformated cert
- * @param bool $heads True if we want to include head and footer
+ * @param string $x509cert A x509 unformated cert
+ * @param bool $heads True if we want to include head and footer
*
* @return string $x509 Formatted cert
*/
+ public static function formatCert($x509cert, $heads = true)
+ {
+ if (is_null($x509cert)) {
+ return;
+ }
- public static function formatCert($cert, $heads = true)
- {
- $x509cert = str_replace(array("\x0D", "\r", "\n"), "", $cert);
- if (!empty($x509cert)) {
- $x509cert = str_replace('-----BEGIN CERTIFICATE-----', "", $x509cert);
- $x509cert = str_replace('-----END CERTIFICATE-----', "", $x509cert);
- $x509cert = str_replace(' ', '', $x509cert);
-
- if ($heads) {
- $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
- }
+ if (strpos($x509cert, '-----BEGIN CERTIFICATE-----') !== false) {
+ $x509cert = static::getStringBetween($x509cert, '-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----');
+ }
- }
- return $x509cert;
- }
+ $x509cert = str_replace(array("\x0d", "\r", "\n", " "), '', $x509cert);
+
+ if ($heads && $x509cert !== '') {
+ $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n";
+ }
+
+ return $x509cert;
+ }
/**
* Returns a private key (adding header & footer if required).
@@ -300,6 +302,7 @@ public static function getStringBetween($str, $start, $end)
* @param bool $stay True if we want to stay (returns the url string) False to redirect
*
* @return string|null $url
+ * @phpstan-return ($stay is true ? string : never)
*
* @throws OneLogin_Saml2_Error
*/
@@ -706,8 +709,14 @@ protected static function buildWithBaseURLPath($info)
if (!empty($baseURLPath)) {
$result = $baseURLPath;
if (!empty($info)) {
- $path = explode('/', $info);
- $extractedInfo = array_pop($path);
+ $extractedInfo = $info;
+ if ($baseURLPath != '/') {
+ // Remove base path from the path info.
+ $extractedInfo = str_replace($baseURLPath, '', $info);
+ }
+
+ // Remove starting and ending slash.
+ $extractedInfo = trim($extractedInfo, '/');
if (!empty($extractedInfo)) {
$result .= $extractedInfo;
}
@@ -725,6 +734,10 @@ protected static function buildWithBaseURLPath($info)
*/
public static function extractOriginalQueryParam($name)
{
+ if (!isset($_SERVER['QUERY_STRING']) || empty($_SERVER['QUERY_STRING'])) {
+ return '';
+ }
+
$index = strpos($_SERVER['QUERY_STRING'], $name.'=');
$substring = substr($_SERVER['QUERY_STRING'], $index + strlen($name) + 1);
$end = strpos($substring, '&');
@@ -1506,12 +1519,41 @@ public static function validateBinarySign($messageType, $getData, $idpData, $ret
}
if ($retrieveParametersFromServer) {
+ if (!isset($_SERVER['QUERY_STRING']) || empty($_SERVER['QUERY_STRING'])) {
+ throw new OneLogin_Saml2_Error(
+ "No query string provided",
+ OneLogin_Saml2_Error::INVALID_PARAMETER
+ );
+ }
+ $keys = array("SAMLRequest", "SAMLResponse", "RelayState", "SigAlg", "Signature");
+ foreach ($keys as $key) {
+ if (substr_count($_SERVER['QUERY_STRING'], $key) > 1) {
+ throw new OneLogin_Saml2_Error(
+ "Duplicate parameter in query string",
+ OneLogin_Saml2_Error::INVALID_PARAMETER
+ );
+ }
+ }
+ if (substr_count($_SERVER['QUERY_STRING'], "SAMLRequest") > 0 && substr_count($_SERVER['QUERY_STRING'], "SAMLResponse") > 0) {
+ throw new OneLogin_Saml2_Error(
+ "Both SAMLRequest and SAMLResponse provided",
+ OneLogin_Saml2_Error::INVALID_PARAMETER
+ );
+ }
+
$signedQuery = $messageType.'='.OneLogin_Saml2_Utils::extractOriginalQueryParam($messageType);
if (isset($getData['RelayState'])) {
$signedQuery .= '&RelayState='.OneLogin_Saml2_Utils::extractOriginalQueryParam('RelayState');
}
$signedQuery .= '&SigAlg='.OneLogin_Saml2_Utils::extractOriginalQueryParam('SigAlg');
} else {
+ if (isset($getData['SAMLRequest']) && isset($getData['SAMLResponse'])) {
+ throw new OneLogin_Saml2_Error(
+ "Both SAMLRequest and SAMLResponse provided",
+ OneLogin_Saml2_Error::INVALID_PARAMETER
+ );
+ }
+
$signedQuery = $messageType.'='.urlencode($getData[$messageType]);
if (isset($getData['RelayState'])) {
$signedQuery .= '&RelayState='.urlencode($getData['RelayState']);
diff --git a/lib/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd b/lib/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd
index 8513959a..12ef3d42 100644
--- a/lib/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd
+++ b/lib/Saml2/schemas/saml-schema-authn-context-types-2.0.xsd
@@ -63,7 +63,7 @@
- Refers to those characterstics that describe how the
+ Refers to those characteristics that describe how the
'secret' (the knowledge or possession
of which allows the Principal to authenticate to the
Authentication Authority) is kept secure
@@ -429,7 +429,7 @@
This element indicates that the Authenticator has been
- transmitted using a transport mechnanism protected by an SSL or TLS
+ transmitted using a transport mechanism protected by an SSL or TLS
session.
diff --git a/lib/Saml2/version.json b/lib/Saml2/version.json
index ac6f7011..52c3af78 100644
--- a/lib/Saml2/version.json
+++ b/lib/Saml2/version.json
@@ -1,6 +1,6 @@
{
"php-saml": {
- "version": "2.19.1",
- "released": "02/03/2021"
+ "version": "2.21.0",
+ "released": "25/05/2025"
}
}
diff --git a/tests/ZendModStandard/Sniffs/Debug/CodeAnalyzerSniff.php b/tests/ZendModStandard/Sniffs/Debug/CodeAnalyzerSniff.php
index d926ee38..675d86f3 100644
--- a/tests/ZendModStandard/Sniffs/Debug/CodeAnalyzerSniff.php
+++ b/tests/ZendModStandard/Sniffs/Debug/CodeAnalyzerSniff.php
@@ -75,11 +75,11 @@ public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
// There is the possibility to pass "--ide" as an option to the analyzer.
// This would result in an output format which would be easier to parse.
- // The problem here is that no cleartext error messages are returnwd; only
+ // The problem here is that no cleartext error messages are returned; only
// error-code-labels. So for a start we go for cleartext output.
$exitCode = exec($cmd, $output, $retval);
- // $exitCode is the last line of $output if no error occures, on error it
+ // $exitCode is the last line of $output if no error occurs, on error it
// is numeric. Try to handle various error conditions and provide useful
// error reporting.
if (is_numeric($exitCode) === true && $exitCode > 0) {
diff --git a/tests/ZendModStandard/ruleset.xml b/tests/ZendModStandard/ruleset.xml
index 80c14224..2a3eddc4 100644
--- a/tests/ZendModStandard/ruleset.xml
+++ b/tests/ZendModStandard/ruleset.xml
@@ -1,6 +1,6 @@
- A coding standard based on an early Zend Framework coding standard. Note that this standard is out of date. And removed the line lenght limitation
+ A coding standard based on an early Zend Framework coding standard. Note that this standard is out of date. And removed the line length limitation
diff --git a/tests/certs/with.comment.crt b/tests/certs/with.comment.crt
new file mode 100644
index 00000000..ed0e9729
--- /dev/null
+++ b/tests/certs/with.comment.crt
@@ -0,0 +1,17 @@
+# certificate comments should be ignored
+-----BEGIN CERTIFICATE-----
+MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMC
+Tk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYD
+VQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG
+9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4
+MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xi
+ZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2Zl
+aWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5v
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LO
+NoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHIS
+KOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d
+1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8
+BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7n
+bK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2Qar
+Q4/67OZfHd7R+POBXhophSMv1ZOo
+-----END CERTIFICATE-----
diff --git a/tests/data/metadata/idp/idp_metadata_multi_certs.xml b/tests/data/metadata/idp/idp_metadata_multi_certs.xml
index f993f64a..90d36ff0 100644
--- a/tests/data/metadata/idp/idp_metadata_multi_certs.xml
+++ b/tests/data/metadata/idp/idp_metadata_multi_certs.xml
@@ -1,5 +1,5 @@
-
+
@@ -68,8 +68,8 @@ WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==
-
+
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
-
+
-
\ No newline at end of file
+
diff --git a/tests/data/metadata/idp/idp_metadata_multi_signing_certs.xml b/tests/data/metadata/idp/idp_metadata_multi_signing_certs.xml
index 0cba257a..ef436f68 100644
--- a/tests/data/metadata/idp/idp_metadata_multi_signing_certs.xml
+++ b/tests/data/metadata/idp/idp_metadata_multi_signing_certs.xml
@@ -1,5 +1,5 @@
-
+
@@ -68,8 +68,8 @@ WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==
-
+
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
-
+
diff --git a/tests/data/metadata/idp/metadata.xml b/tests/data/metadata/idp/metadata.xml
index c2ca6739..decf301e 100644
--- a/tests/data/metadata/idp/metadata.xml
+++ b/tests/data/metadata/idp/metadata.xml
@@ -1,5 +1,5 @@
-
+
@@ -68,8 +68,8 @@ WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==
-
+
urn:oasis:names:tc:SAML:2.0:nameid-format:transient
-
+
-
\ No newline at end of file
+
diff --git a/tests/data/metadata/idp/shib_metadata.xml b/tests/data/metadata/idp/shib_metadata.xml
index 5196db56..c28814c3 100644
--- a/tests/data/metadata/idp/shib_metadata.xml
+++ b/tests/data/metadata/idp/shib_metadata.xml
@@ -1,7 +1,7 @@