diff --git a/bundle.sh b/bundle.sh new file mode 100755 index 0000000..0336c0f --- /dev/null +++ b/bundle.sh @@ -0,0 +1,10 @@ +#!/bin/sh +# This script can be used to make it easier to upgrade the spid-php-lib dependency of this plugin +# Run it from the root of the project + +set -e +rm -rf spid-php-lib +composer install --no-dev +rsync -rvx vendor/italia/spid-php-lib . +cd spid-php-lib +composer install --no-dev diff --git a/composer.json b/composer.json index 4b6676e..7116586 100644 --- a/composer.json +++ b/composer.json @@ -7,8 +7,15 @@ "name": "Paolo Greppi", "email": "paolo.greppi@libpf.com" }], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/italia/spid-php-lib" + } + ], "require": { - + "php": ">=7.0", + "italia/spid-php-lib": "dev-feature/issue-65-certs#0b58ecd32b4909ff207f2a47b3dcac8b7507b2fb as 0.30.0" }, "require-dev": { "squizlabs/php_codesniffer": "*", @@ -35,4 +42,4 @@ "phpcbf": "Fix coding standards warnings/errors automatically with PHP Code Beautifier" } } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index e06268c..7f4c0dc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,23 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "e7bed20333f325cbaa4bd88aa9622ad3", - "content-hash": "c7619beafa0ffd3bb37faa6d099333b8", + "hash": "6a26dc18059cfba09de6d0e7692726c0", + "content-hash": "9cf78f8448b0f75b39d6bd487afd55d4", "packages": [ { "name": "italia/spid-php-lib", - "version": "v0.20", + "version": "dev-feature/issue-65-certs", "source": { "type": "git", "url": "https://github.com/italia/spid-php-lib.git", - "reference": "49c743bb668256ee4a12346b925fe8cac0785512" + "reference": "0b58ecd32b4909ff207f2a47b3dcac8b7507b2fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/italia/spid-php-lib/zipball/49c743bb668256ee4a12346b925fe8cac0785512", - "reference": "49c743bb668256ee4a12346b925fe8cac0785512", + "url": "https://api.github.com/repos/italia/spid-php-lib/zipball/0b58ecd32b4909ff207f2a47b3dcac8b7507b2fb", + "reference": "0b58ecd32b4909ff207f2a47b3dcac8b7507b2fb", "shasum": "" }, + "archive": { + "exclude": [ + "/example", + "/tests" + ] + }, "require": { "php": "^7.0", "robrichards/xmlseclibs": "^3.0" @@ -35,7 +41,6 @@ "Italia\\Spid\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -50,7 +55,11 @@ } ], "description": "PHP package for SPID authentication", - "time": "2018-10-12 14:03:16" + "support": { + "source": "https://github.com/italia/spid-php-lib/tree/feature/issue-65-certs", + "issues": "https://github.com/italia/spid-php-lib/issues" + }, + "time": "2018-10-20 13:46:29" }, { "name": "robrichards/xmlseclibs", @@ -93,60 +102,1695 @@ ], "packages-dev": [ { - "name": "squizlabs/php_codesniffer", - "version": "3.3.2", + "name": "dealerdirect/phpcodesniffer-composer-installer", + "version": "v0.4.4", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", - "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/2e41850d5f7797cbb1af7b030d245b3b24e63a08", + "reference": "2e41850d5f7797cbb1af7b030d245b3b24e63a08", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "composer-plugin-api": "^1.0", + "php": "^5.3|^7", + "squizlabs/php_codesniffer": "*" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "composer/composer": "*", + "wimg/php-compatibility": "^8.0" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" + "suggest": { + "dealerdirect/qa-tools": "All the PHP QA tools you'll need" + }, + "type": "composer-plugin", + "extra": { + "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + }, + "autoload": { + "psr-4": { + "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Franck Nijhof", + "email": "f.nijhof@dealerdirect.nl", + "homepage": "http://workingatdealerdirect.eu", + "role": "Developer" + } + ], + "description": "PHP_CodeSniffer Standards Composer Installer Plugin", + "homepage": "http://workingatdealerdirect.eu", + "keywords": [ + "PHPCodeSniffer", + "PHP_CodeSniffer", + "code quality", + "codesniffer", + "composer", + "installer", + "phpcs", + "plugin", + "qa", + "quality", + "standard", + "standards", + "style guide", + "stylecheck", + "tests" ], + "time": "2017-12-06 16:27:17" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "myclabs/deep-copy", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/collections": "^1.0", + "doctrine/common": "^2.6", + "phpunit/phpunit": "^4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + }, + "files": [ + "src/DeepCopy/deep_copy.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2017-10-19 19:58:43" + }, + { + "name": "phar-io/manifest", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", + "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-phar": "*", + "phar-io/version": "^1.0.1", + "php": "^5.6 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" } }, + "autoload": { + "classmap": [ + "src/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "time": "2017-03-05 18:14:27" + }, + { + "name": "phar-io/version", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", + "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "time": "2017-03-05 17:38:23" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", "keywords": [ - "phpcs", - "standards" + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" ], - "time": "2018-09-23 23:08:17" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], + "time": "2017-09-11 18:02:19" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "4.3.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", + "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "shasum": "" + }, + "require": { + "php": "^7.0", + "phpdocumentor/reflection-common": "^1.0.0", + "phpdocumentor/type-resolver": "^0.4.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "doctrine/instantiator": "~1.0.5", + "mockery/mockery": "^1.0", + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2017-11-30 07:14:17" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", + "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", + "shasum": "" + }, + "require": { + "php": "^5.5 || ^7.0", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2017-07-14 14:27:02" + }, + { + "name": "phpspec/prophecy", + "version": "1.8.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", + "sebastian/comparator": "^1.1|^2.0|^3.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5|^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2018-08-05 17:53:17" + }, + { + "name": "phpunit/php-code-coverage", + "version": "5.3.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", + "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-xmlwriter": "*", + "php": "^7.0", + "phpunit/php-file-iterator": "^1.4.2", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-token-stream": "^2.0.1", + "sebastian/code-unit-reverse-lookup": "^1.0.1", + "sebastian/environment": "^3.0", + "sebastian/version": "^2.0.1", + "theseer/tokenizer": "^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-xdebug": "^2.5.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2018-04-06 15:36:58" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2017-11-27 13:52:08" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2017-02-26 11:10:40" + }, + { + "name": "phpunit/php-token-stream", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "791198a2c6254db10131eecfe8c06670700904db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", + "reference": "791198a2c6254db10131eecfe8c06670700904db", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2017-11-27 05:48:46" + }, + { + "name": "phpunit/phpunit", + "version": "6.5.13", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "myclabs/deep-copy": "^1.6.1", + "phar-io/manifest": "^1.0.1", + "phar-io/version": "^1.0", + "php": "^7.0", + "phpspec/prophecy": "^1.7", + "phpunit/php-code-coverage": "^5.3", + "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-text-template": "^1.2.1", + "phpunit/php-timer": "^1.0.9", + "phpunit/phpunit-mock-objects": "^5.0.9", + "sebastian/comparator": "^2.1", + "sebastian/diff": "^2.0", + "sebastian/environment": "^3.1", + "sebastian/exporter": "^3.1", + "sebastian/global-state": "^2.0", + "sebastian/object-enumerator": "^3.0.3", + "sebastian/resource-operations": "^1.0", + "sebastian/version": "^2.0.1" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2", + "phpunit/dbunit": "<3.0" + }, + "require-dev": { + "ext-pdo": "*" + }, + "suggest": { + "ext-xdebug": "*", + "phpunit/php-invoker": "^1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2018-09-08 15:10:43" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "5.0.10", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.5", + "php": "^7.0", + "phpunit/php-text-template": "^1.2.1", + "sebastian/exporter": "^3.1" + }, + "conflict": { + "phpunit/phpunit": "<6.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5.11" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2018-08-09 05:50:03" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2017-03-04 06:30:41" + }, + { + "name": "sebastian/comparator", + "version": "2.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", + "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/diff": "^2.0 || ^3.0", + "sebastian/exporter": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2018-02-01 13:46:46" + }, + { + "name": "sebastian/diff", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2017-08-03 08:09:46" + }, + { + "name": "sebastian/environment", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2017-07-01 08:51:00" + }, + { + "name": "sebastian/exporter", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", + "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2017-04-03 13:19:02" + }, + { + "name": "sebastian/global-state", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2017-04-27 15:39:26" + }, + { + "name": "sebastian/object-enumerator", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", + "shasum": "" + }, + "require": { + "php": "^7.0", + "sebastian/object-reflector": "^1.1.1", + "sebastian/recursion-context": "^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2017-08-03 12:35:26" + }, + { + "name": "sebastian/object-reflector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "773f97c67f28de00d397be301821b06708fca0be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", + "reference": "773f97c67f28de00d397be301821b06708fca0be", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "time": "2017-03-29 09:07:27" + }, + { + "name": "sebastian/recursion-context", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", + "shasum": "" + }, + "require": { + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2017-03-03 06:23:57" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", + "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03 07:35:21" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.3.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2018-09-23 23:08:17" + }, + { + "name": "theseer/tokenizer", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^7.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "time": "2017-04-07 12:08:54" + }, + { + "name": "webmozart/assert", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.6", + "sebastian/version": "^1.0.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2018-01-29 19:49:41" + }, + { + "name": "wimg/php-compatibility", + "version": "9.0.0", + "source": { + "type": "git", + "url": "https://github.com/PHPCompatibility/PHPCompatibility.git", + "reference": "e9f4047e5edf53c88f36f1dafc0d49454ce13e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibility/zipball/e9f4047e5edf53c88f36f1dafc0d49454ce13e25", + "reference": "e9f4047e5edf53c88f36f1dafc0d49454ce13e25", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.3 || ^3.0.2" + }, + "conflict": { + "squizlabs/php_codesniffer": "2.6.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.5 || ^5.0 || ^6.0 || ^7.0" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically.", + "roave/security-advisories": "dev-master || Helps prevent installing dependencies with known security issues." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/PHPCompatibility/PHPCompatibility/graphs/contributors" + }, + { + "name": "Wim Godden", + "homepage": "https://github.com/wimg", + "role": "lead" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl", + "role": "lead" + } + ], + "description": "A set of sniffs for PHP_CodeSniffer that checks for PHP cross-version compatibility.", + "homepage": "http://techblog.wimgodden.be/tag/codesniffer/", + "keywords": [ + "compatibility", + "phpcs", + "standards" + ], + "time": "2018-10-07 17:38:02" + }, + { + "name": "wp-coding-standards/wpcs", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards.git", + "reference": "46d42828ce7355d8b3776e3171f2bda892d179b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/WordPress-Coding-Standards/WordPress-Coding-Standards/zipball/46d42828ce7355d8b3776e3171f2bda892d179b4", + "reference": "46d42828ce7355d8b3776e3171f2bda892d179b4", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "squizlabs/php_codesniffer": "^2.9.0 || ^3.0.2" + }, + "require-dev": { + "phpcompatibility/php-compatibility": "*" + }, + "suggest": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.4.3 || This Composer plugin will sort out the PHPCS 'installed_paths' automatically." + }, + "type": "phpcodesniffer-standard", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Contributors", + "homepage": "https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/graphs/contributors" + } + ], + "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", + "keywords": [ + "phpcs", + "standards", + "wordpress" + ], + "time": "2018-09-10 17:04:05" + } + ], + "aliases": [ + { + "alias": "0.30.0", + "alias_normalized": "0.30.0.0", + "version": "dev-feature/issue-65-certs", + "package": "italia/spid-php-lib" + } + ], + "minimum-stability": "stable", + "stability-flags": { + "italia/spid-php-lib": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/spid-php-lib/.travis.yml b/spid-php-lib/.travis.yml index 0f741aa..a419cbf 100644 --- a/spid-php-lib/.travis.yml +++ b/spid-php-lib/.travis.yml @@ -12,7 +12,6 @@ cache: install: - composer install --no-interaction - mkdir -p example/idp_metadata - - make -C example - php bin/download_idp_metadata.php ./example/idp_metadata script: diff --git a/spid-php-lib/README.md b/spid-php-lib/README.md index acc8d01..9b89db3 100644 --- a/spid-php-lib/README.md +++ b/spid-php-lib/README.md @@ -5,7 +5,7 @@ [![SPID on forum.italia.it](https://img.shields.io/badge/Forum-SPID-blue.svg)](https://forum.italia.it/c/spid) [![Build Status](https://travis-ci.org/italia/spid-php-lib.svg?branch=master)](https://travis-ci.org/italia/spid-php-lib) -> ⚠️ **WORK IN PROGRESS (but should be useable)** ⚠️ +> **CURRENT VERSION: v0.20** # spid-php-lib PHP package for SPID authentication. @@ -16,10 +16,223 @@ Alternatives for PHP: - [spid-php](https://github.com/italia/spid-php) based on [SimpleSAMLphp](https://simplesamlphp.org/) - [spid-php2](https://github.com/simevo/spid-php2) based on [php-saml](https://github.com/onelogin/php-saml) +Framework specific libraries and examples based on spid-php-lib: +- [https://github.com/italia/spid-symfony-bundle](https://github.com/italia/spid-symfony-bundle) +- [https://github.com/simevo/spid-symfony3-example](https://github.com/simevo/spid-symfony3-example) +- [https://github.com/simevo/spid-wordpress](https://github.com/simevo/spid-wordpress) + Alternatives for other languages: - [spid-perl](https://github.com/italia/spid-perl) - [spid-ruby](https://github.com/italia/spid-ruby) + + +Table of Contents +================= + +* [Repository layout](#repository-layout) +* [Getting started](#getting-started) + * [Prerequisites](#prerequisites) + * [Configuring & Installing](#configuring-and-installing) + * [Usage](#usage) + * [Performing login](#performing-login) + * [Performing logout](#performing-logout) + * [Complete API](#complete-api) + * [Example](#example) + * [Demo application](#demo-application) +* [Features](#features) + * [More features](#more-features) +* [Troubleshooting](#troubleshooting) +* [Testing](#testing) + * [Unit Tests](#unit-tests) + * [Linting](#linting) +* [Contributing](#contributing) +* [See also](#see-also) +* [Authors](#authors) +* [License](#license) + + +## Repository layout + +* [bin/](bin/) auxiliary scripts +* [example/](example/) contains a demo application +* [src/](src/) contains the library implementation +* [test/](test/) contains the unit tests + +## Getting Started + +Tested on: amd64 Debian 9.5 (stretch, current stable) with PHP 7.0. + +Supports PHP 7.0, 7.1 and 7.2. + +### Prerequisites + +```sh +sudo apt install composer make openssl php-curl php-zip php-xml +``` + +### Configuring and Installing + + +**NOTE**: during testing, please use the test Identity Provider [spid-testenv2](https://github.com/italia/spid-testenv2). + + +1. Install with composer + + ```composer require italia/spid-php-lib``` + +2. Generate key and certificate files for your Service Provider (SP). + + Example: + ```openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -subj "/C=IT/ST=Italy/L=Milan/O=myservice/CN=localhost" -keyout sp.key -out sp.crt``` + +3. Download the Identity Provider (IdP) metadata files and place them in a directory in your project, for example `idp_metadata`. + A convenience tool is provided to download those of the production IdPs: [vendor/italia/spid-php-lib/bin/download_idp_metadata.php](bin/download_idp_metadata.php), example usage: + ```sh + mkdir idp_metadata + php vendor/italia/spid-php-lib/bin/download_idp_metadata.php ./idp_metadata + ``` + + *TEST ENVIRONMENT: If you are using [spid-testenv2](https://github.com/italia/spid-testenv2), manually download the IdP metadata and place it in your `idp_metadata` folder* + +4. Make your SP known to IdPs: for production follow the guidelines at [https://www.spid.gov.it/come-diventare-fornitore-di-servizi-pubblici-e-privati-con-spid](https://www.spid.gov.it/come-diventare-fornitore-di-servizi-pubblici-e-privati-con-spid) + + *TEST ENVIRONMENT: simply download your Service Provider (SP) metadata and place it in the appropriate folder of the [test environment](https://github.com/italia/spid-testenv2). The test environment must be restarted after every change to the SP metadata.* + + + +### Usage + +All classes provided by this package reside in the `Italia\Spid` namespace. +More detailed documentaion is available in the [SAMLInterface.php](/src/Spid/Interfaces/SAMLInterface.php) file. + +Load them using the composer-generated autoloader: +```php +require_once(__DIR__ . "/vendor/autoload.php"); +``` + +The main class is `Italia\Spid\Sp` (service provider). + +Generate a settings array following this guideline + +```php +$settings = array( + 'sp_entityid' => SP_BASE_URL, // preferred: https protocol, no trailing slash, example: https://sp.example.com/ + 'sp_key_file' => '/path/to/sp.key', + 'sp_cert_file' => '/path/to/sp.crt', + 'sp_assertionconsumerservice' => [ + // order is important ! the 0-base index in this array will be used as ID in the calls + SP_BASE_URL . '/acs', + ... + ], + 'sp_singlelogoutservice' => [ + // order is important ! the 0-base index in this array will be used as ID in the calls + [SP_BASE_URL . '/slo', 'POST'], + [SP_BASE_URL . '/slo', 'REDIRECT'] + ... + ], + 'sp_org_name' => 'your organization full name', + 'sp_org_display_name' => 'your organization display name', + 'idp_metadata_folder' => '/path/to/idp_metadata/', + 'sp_attributeconsumingservice' => [ + // order is important ! the 0-base index in this array will be used as ID in the calls + ["fiscalNumber"], + ["name", "familyName", "fiscalNumber", "email", "spidCode"], + ... + ] +); +``` + +then initialise the main Sp class + +```php +$sp = new Italia\Spid\Sp($settings); +``` + +#### Performing login + + +```php +// shortname of IdP, same as the name of corresponding IdP metadata file, without .xml +$idpName = 'testenv'; +// index of assertion consumer service as per the SP metadata (sp_assertionconsumerservice in settings array) +$assertId = 0; +// index of attribute consuming service as per the SP metadata (sp_attributeconsumingservice in settings array) +$attrId = 1; + +// Generate the login URL and redirect to the IdP login page +$sp->login($idpName, $assertId, $attrId); +``` +Complete the login operation by calling +```php +$sp->isAuthenticated(); +``` +at the assertion consumer service URL. + +Then call +```php +$userAttributes = $sp->getAttributes(); +``` +to receive an array of the requested user attributes. + +#### Performing logout + +Call +```php +// index of single logout service as per the SP metadata (sp_singlelogoutservice in settings array) +$sloId = 0; + +$sp->logout($sloId); +``` +The method will redirect to the IdP Single Logout page, or return false if you are not logged in. + +#### Complete API + +|**Method**|**Description**| +|:---|:---| +|loadIdpFromFile(string $filename)|loads an `Idp` object by parsing the provided XML at `$filename`| +|getIdpList() : array|loads all the `Idp` objects from the `idp_metadata_folder` provided in settings| +|getIdp(string $filename)|alias of `loadIdpFromFile`| +|getSPMetadata() : string|returns the SP metadata as a string| +|login(string $idpFilename, int $assertID, int $attrID, $level = 1, string $redirectTo = null, $shouldRedirect = true)|login with REDIRECT binding. Use `$idpFilename` to select in IdP for login by indicating the name (without extension) of an XML file in your `idp_metadata_folder`. `$assertID` and `$attrID` indicate respectively the array index of `sp_assertionconsumerservice` and `sp_attributeconsumingservice` provided in settings. Optional parameters: `$level` for SPID authentication level (1, 2 or 3), `$redirectTo` to indicate an url to redirect to after login, `$shouldRedirect` to indicate if the login function should automatically redirect to the IdP or should return the login url as a string| +|loginPost(string $idpName, int $ass, int $attr, $level = 1, string $redirectTo = null, $shouldRedirect = true)|like login, but uses POST binding| +|logout(int $slo, string $redirectTo = null, $shouldRedirect = true)|logout with REDIRECT binding. `$slo` indicates the array index of the `sp_singlelogoutservice` provided in settings. Optional parameters: `$redirectTo` to indicate an url to redirect to after login, `$shouldRedirect` to indicate if the login function should automatically redirect to the IdP or should return the login url as a string| +|logoutPost(int $slo, string $redirectTo = null, $shouldRedirect = true)|like logout, but uses POST binding| +|isAuthenticated() : bool|checks if the user is authenticated. This method **MUST** be caled after login and logout to finalise the operation.| +|getAttributes() : array|If you requested attributes with an attribute consuming service during login, this method will return them in array format| +|isConfigured() : bool|Returns true if the SP certificates are found where the settings says they are, and they are valid (i.e. the library has been configured correctly)| +|configure(string $countryName, string $stateName, string $localityName, string $commonName, string $emailAddress)|Generates the SP key and certificate (validity = 10 years) where the settings says they should be; this function should be used with care because it requires write access to the filessystem, and invalidates the metadata| + +### Example + +A basic demo application is provided in the [example/](example/) directory of this repository. + +**/example and /tests folders are NOT provided with the production version from packagist, remember to require the `dev-develop` version or just clone this repository (advised)** + +To try it out: + +1. Generate a test certificate and key pair with: + + ```sh + openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -subj "/C=IT/ST=Italy/L=Milan/O=myservice/CN=localhost" -keyout sp.key -out sp.crt + ``` + +2. Adapt the hostname of the SP changing the `$base` variable in the `example/index.php` file; the browser you'll be testing from must be able to resolve the FQDN (the default is `https://sp.example.com`). Using HTTPS is strongly suggested. + +3. Configure and install the test IdP [spid-testenv2](https://github.com/italia/spid-testenv2) + +4. Serve the `example` dir from your preferred webserver + +5. Visit https://sp.example.com/metadata to get the SP metadata, then copy these over to the IdP and register the SP with the IdP + +6. Visit https://idp.example.com/metadata to get the IdP metadata, then save it as `example/idp_metadata/testenv.xml` to register the IdP with the SP + +7. Visit: https://sp.example.com and click `login`. + +#### Demo application + +A Docker-based demo application is available at [https://github.com/simevo/spid-php-lib-example](https://github.com/simevo/spid-php-lib-example). + ## Features - provides a **lean implementation** without relying on external SAML packages @@ -94,106 +307,7 @@ Alternatives for other languages: ### More features -* [ ] Generation of SPID button markup - -## Repository layout - -* [bin/](bin/) auxiliary scripts -* [example/](example/) contains a demo application -* [src/](src/) contains the library implementation -* [test/](test/) contains the unit tests - -## Getting Started - -Tested on: amd64 Debian 9.5 (stretch, current stable) with PHP 7.0. - -Supports PHP 7.0, 7.1 and 7.2. - -### Prerequisites - -```sh -sudo apt install composer make openssl php-curl php-zip php-xml -``` - -### Configuring and Installing - -Before using this package, you must: - -1. Install prerequisites with composer: -```sh -composer install --no-dev -``` - -2. Generate key and certificate for the Service Provider (SP) - -3. Download and verify the Identity Provider (IdP) metadata files; it is advised to place them in a separate `idp_metadata` directory. A convenience tool is provided to download those of the production IdPs: [bin/download_idp_metadata.php](bin/download_idp_metadata.php), example usage: -```sh -bin/download_idp_metadata.php ./example/idp_metadata -``` - -4. Reciprocally configure the SP and the IdPs to talk to each other by exchanging their metadata. - -**NOTE**: during testing, it is highly adviced to use the test Identity Provider [spid-testenv2](https://github.com/italia/spid-testenv2). - -### Usage - -All classes provided by this package reside in the `Italia\Spid` namespace. - -Load them using the composer-generated autoloader: -```php -require_once(__DIR__ . "/../vendor/autoload.php"); -``` - -The main class is `Italia\Spid\Sp` (service provider), sample instantiation: - -```php -$settings = array( - 'sp_entityid' => 'https://example.com/myservice', - 'idp_metadata_folder' => './idp_metadata/', - ... -); -$sp = new Italia\Spid\Sp($settings); -``` - -By default the the service provider loads all IdP metadata found in the specified `idp_metadata_folder` and is ready for use, as in: - -```php -// shortname of IdP, same as the name of corresponding IdP metadata file, without .xml -$idpName = 'testenv'; -// return url -$returnTo = 'https://example.com/return_to_url'; -// index of assertion consumer service as per the SP metadata -$assertId = 0; -// index of attribute consuming service as per the SP metadata -$attrId = 1; -// SPID level (1, 2 or 3) -$spidLevel = 1; -$sp->login($idpName, $assertId, $attrId, $redirectTo, $spidLevel); -... -$attributes = $sp->getAttributes(); -var_dump($attributes); -$sp->logout(); -``` - -### Example - -A basic demo application is provided in the [example/](example/) directory. - -To try it out: - -1. Configure and install this package (see above steps 1 to 3) - -2. Adapt the hostname of the SP changing the `$base` variable in the `example/index.php` file; the browser you'll be testing from must be able to resolve the FQDN (the default is `sp.example.com`) - -3. Configure and install the test IsP [spid-testenv2](https://github.com/italia/spid-testenv2) - -4. Serve the `example` dir from your preferred webserver - -5. Visit https://sp.example.com/metadata to get the SP metadata, then copy these over to the IdP and register the SP with the IdP - -6. Visit https://idp.example.com/metadata to get the IdP metadata, then save it as `example/idp_metadata/testenv.xml` to register the IdP with the SP - -7. Visit: https://sp.example.com and click `login`. +* [x] Generation of SPID button markup ## Troubleshooting @@ -214,12 +328,20 @@ In addition, you can use the [SAML Developer Tools](https://www.samltool.com/onl ## Testing +To test and lint this package you must place yourself in its root directory, then follow the provided instructions. + +Assuming you followed the installation instructions with composer, simply do: + +```sh +cd vendor/italia/spid-php-lib +``` + ### Unit tests -Install prerequisites with composer and generate key and certificate for the SP with: +Install prerequisites with composer, generate key and certificate for the SP and download the metadata for all current production IdPs with: ```sh composer install -make -C example/ +bin/download_idp_metadata.php example/idp_metadata ``` then launch the unit tests with PHPunit: @@ -231,7 +353,8 @@ then launch the unit tests with PHPunit: This project complies with the [PSR-2: Coding Style Guide](https://www.php-fig.org/psr/psr-2/). -Lint the code with: +Make sure you are in the package directory, then lint the code with: + ``` ./vendor/bin/phpcs --standard=PSR2 xxx.php ``` diff --git a/spid-php-lib/bin/download_idp_metadata.php b/spid-php-lib/bin/download_idp_metadata.php index 313af7a..303b922 100755 --- a/spid-php-lib/bin/download_idp_metadata.php +++ b/spid-php-lib/bin/download_idp_metadata.php @@ -1,6 +1,6 @@ #!/usr/bin/php = 5.4" }, - "suggest": { - "ext-openssl": "OpenSSL extension" - }, "type": "library", "autoload": { "psr-4": { @@ -45,7 +43,7 @@ "xml", "xmldsig" ], - "time": "2017-08-31 09:27:07" + "time": "2018-09-27 13:24:13" } ], "packages-dev": [ @@ -716,16 +714,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.12", + "version": "6.5.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e" + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/24da433d7384824d65ea93fbb462e2f31bbb494e", - "reference": "24da433d7384824d65ea93fbb462e2f31bbb494e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", + "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", "shasum": "" }, "require": { @@ -796,7 +794,7 @@ "testing", "xunit" ], - "time": "2018-08-22 06:32:48" + "time": "2018-09-08 15:10:43" }, { "name": "phpunit/phpunit-mock-objects", @@ -1418,16 +1416,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.3.1", + "version": "3.3.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "628a481780561150481a9ec74709092b9759b3ec" + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/628a481780561150481a9ec74709092b9759b3ec", - "reference": "628a481780561150481a9ec74709092b9759b3ec", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/6ad28354c04b364c3c71a34e4a18b629cc3b231e", + "reference": "6ad28354c04b364c3c71a34e4a18b629cc3b231e", "shasum": "" }, "require": { @@ -1465,7 +1463,7 @@ "phpcs", "standards" ], - "time": "2018-07-26 23:47:18" + "time": "2018-09-23 23:08:17" }, { "name": "theseer/tokenizer", @@ -1563,6 +1561,8 @@ "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, - "platform": [], + "platform": { + "php": "^7.0" + }, "platform-dev": [] } diff --git a/spid-php-lib/src/Sp.php b/spid-php-lib/src/Sp.php index 4367b27..337d951 100644 --- a/spid-php-lib/src/Sp.php +++ b/spid-php-lib/src/Sp.php @@ -31,7 +31,7 @@ public function __call($method, $arguments) { $methods_implemented = get_class_methods($this->protocol); if (!in_array($method, $methods_implemented)) { - throw new \Exception("Invalid method requested", 1); + throw new \Exception("Invalid method [$method] requested", 1); } return call_user_func_array(array($this->protocol, $method), $arguments); } diff --git a/spid-php-lib/src/Spid/Interfaces/IdpInterface.php b/spid-php-lib/src/Spid/Interfaces/IdpInterface.php index 98d5070..f118b41 100644 --- a/spid-php-lib/src/Spid/Interfaces/IdpInterface.php +++ b/spid-php-lib/src/Spid/Interfaces/IdpInterface.php @@ -28,7 +28,7 @@ public function authnRequest($ass, $attr, $level, $returnTo = null, $shouldRedir // $returnTo: return url // $shouldRedirect: tells if the function should emit headers and redirect to login URL or return the URL as string // returns and empty string if $shouldRedirect = true, the logout URL otherwhise - public function logoutRequest(Session $session, $slo, $binding, $returnTo = null, $shouldRedirect = true) : string; + public function logoutRequest(Session $session, $slo, $binding, $returnTo = null, $shouldRedirect = true) : string; //generates a logoutResponse in response to an Idp initiated logout request public function logoutResponse(): string; diff --git a/spid-php-lib/src/Spid/Interfaces/ResponseInterface.php b/spid-php-lib/src/Spid/Interfaces/ResponseInterface.php index b9df952..93f32a2 100644 --- a/spid-php-lib/src/Spid/Interfaces/ResponseInterface.php +++ b/spid-php-lib/src/Spid/Interfaces/ResponseInterface.php @@ -5,7 +5,7 @@ interface ResponseInterface { // Validates a received response. - // Throws exceptions on missing or invalid values. + // Throws exceptions on missing or invalid values. // returns false if resposne code is not success // returns true otherwise public function validate($xml, $hasAssertion) : bool; diff --git a/spid-php-lib/src/Spid/Interfaces/SAMLInterface.php b/spid-php-lib/src/Spid/Interfaces/SAMLInterface.php index ef4039d..026e344 100644 --- a/spid-php-lib/src/Spid/Interfaces/SAMLInterface.php +++ b/spid-php-lib/src/Spid/Interfaces/SAMLInterface.php @@ -8,73 +8,93 @@ interface SAMLInterface { // $settings = [ - // 'sp_entityid' => $base, // preferred: https protocol, no trailing slash - // 'sp_key_file' => './sp.key', - // 'sp_cert_file' => './sp.crt', + // 'sp_entityid' => SP_BASE_URL, // preferred: https protocol, no trailing slash, example: https://sp.example.com/ + // 'sp_key_file' => '/path/to/sp.key', + // 'sp_cert_file' => '/path/to/sp.crt', // 'sp_assertionconsumerservice' => [ - // // array of assertion consuming services - // // order is important ! the 0-base index in this array will be used in the calls - // $base . '/acs_route' + // // order is important ! the 0-base index in this array will be used as ID in the calls + // SP_BASE_URL . '/acs', + // ... + // ], + // 'sp_singlelogoutservice' => [ + // // order is important ! the 0-base index in this array will be used as ID in the calls + // [SP_BASE_URL . '/slo', 'POST'], + // [SP_BASE_URL . '/slo', 'REDIRECT'] + // ... // ], - // 'sp_singlelogoutservice' => $base . '/slo_route', // path relative to sp_entityid base url or full url // 'sp_org_name' => 'your organization full name', // 'sp_org_display_name' => 'your organization display name', - // 'idp_metadata_folder' => './idp_metadata/', // path to idp metadata folder + // 'idp_metadata_folder' => '/path/to/idp_metadata/', // 'sp_attributeconsumingservice' => [ - // // array of attribute consuming services - // // order is important ! the 0-base index in this array will be used in the calls - // ["name", "familyName", "fiscalNumber", "email"], - // ["name", "familyName", "fiscalNumber", "email", "spidCode"] - // ] + // // order is important ! the 0-base index in this array will be used as ID in the calls + // ["fiscalNumber"], + // ["name", "familyName", "fiscalNumber", "email", "spidCode"], + // ... // ]; public function __construct(array $settings); - // loads selected Identity Provider - // $filename: file name of the idp to be loaded. Only the file, without the path, needs to be provided. - // returns null or the Idp object. + // loads an Idp object by parsing the provided XML at $filename + // $filename: file name of the IdP to be loaded. Only the file, without the path, needs to be provided. + // returns null or the Idp object. public function loadIdpFromFile(string $filename); - // loads all Idps found in the idp metadata folder provided in settings - // files are loaded with loadIdpFromFile($filename) + // loads all the `Idp` objects from the idp_metadata_folder provided in settings + // the individual files are loaded with loadIdpFromFile($filename) // returns an array mapping filename (without extension) => entityID (used for spid-smart-button) - // if no idps are found returns an empty array + // if no IdPs are found returns an empty array public function getIdpList() : array; // alias of loadIdpFromFile public function getIdp(string $filename); - // returns SP XML metadata as a string + // returns SP metadata as a string public function getSPMetadata() : string; - // performs login - // $idpFilename: shortname of IdP, same as the name of corresponding IdP metadata file, without .xml + // performs login with REDIRECT binding + // $idpFilename: shortname of IdP, same as the name of corresponding IdP metadata file, without .xml extension // $assertID: index of assertion consumer service as per the SP metadata // $attrID: index of attribute consuming service as per the SP metadata // $level: SPID level (1, 2 or 3) - // $returnTo: return url + // $returnTo: url to redirect to after login // $shouldRedirect: tells if the function should emit headers and redirect to login URL or return the URL as string // returns false is already logged in // returns an empty string if $shouldRedirect = true, the login URL otherwhise - public function login(string $idpFilename, int $assertID, int $attrID, $level = 1, string $redirectTo = null, $shouldRedirect = true); + public function login( + string $idpFilename, + int $assertID, + int $attrID, + $level = 1, + string $redirectTo = null, + $shouldRedirect = true + ); // performs login with POST Binding // uses the same parameters and return values as login - public function loginPost(string $idpFilename, int $assertID, int $attrID, $level = 1, string $redirectTo = null, $shouldRedirect = true); + public function loginPost( + string $idpFilename, + int $assertID, + int $attrID, + $level = 1, + string $redirectTo = null, + $shouldRedirect = true + ); - // This method takes the necessary steps to update the user login status, and return a boolean representing the result - // The method checks for any input response and validates it. The validation itself can create or destroy login sessions. + // This method takes the necessary steps to update the user login status, and return a boolean representing the + // result. + // The method checks for any input response and validates it. The validation itself can create or destroy login + // sessions. // After updating the login status as described, return true if login session exists, false otherwise // IMPORTANT NOTICE: AFTER ANY LOGIN/LOGOUT OPERATION YOU MUST CALL THIS METHOD TO FINALIZE THE OPERATION - // CALLING THIS METHOD AFTER LOGIN() WILL IN FACT FINISH THE OPERATION BY VALIDATING THE RESULT AND CREATING THE SESSION - // AND STORING USER ATTRIBUTES. + // CALLING THIS METHOD AFTER LOGIN() WILL IN FACT FINISH THE OPERATION BY VALIDATING THE RESULT AND CREATING THE + // SESSION AND STORING USER ATTRIBUTES. // SIMILARLY, AFTER A LOGOUT() CALLING THIS METHOD WILL VALIDATE THE RESULT AND DESTROY THE SESSION. // LOGIN() AND LOGOUT() ALONE INTERACT WITH THE IDP, BUT DON'T CHECK FOR RESULTS AND UPDATE THE SP public function isAuthenticated() : bool; - // performs logout + // performs logout with REDIRECT binding // $slo: index of the singlelogout service as per the SP metadata - // $returnTo: return url - // $shouldRedirect: tells if the function should emit headers and redirect to login URL or return the URL as string + // $returnTo: url to redirect to after logout + // $shouldRedirect: tells if the function should emit headers and redirect to logout URL or return the URL as string // returns false if not logged in // returns an empty string if $shouldRedirect = true, the logout URL otherwhise public function logout(int $slo, string $redirectTo = null, $shouldRedirect = true); @@ -86,4 +106,12 @@ public function logoutPost(int $slo, string $redirectTo = null, $shouldRedirect // returns attributes as an array or an empty array if not authenticated // example: array('name' => 'Franco', 'familyName' => 'Rossi', 'fiscalNumber' => 'FFFRRR88A12T4441R',) public function getAttributes() : array; + + // returns true if the SP certificates are found where the settings says they are, and they are valid + // (i.e. the library has been configured correctly + public function isConfigured() : bool; + + // Generates with openssl the SP certificates where the settings says they should be + // this function should be used with care because it requires write access to the filesystem, and invalidates the metadata + public function configure(string $countryName, string $stateName, string $localityName, string $commonName, string $emailAddress); } diff --git a/spid-php-lib/src/Spid/Saml.php b/spid-php-lib/src/Spid/Saml.php index ac547c4..2e0bee3 100644 --- a/spid-php-lib/src/Spid/Saml.php +++ b/spid-php-lib/src/Spid/Saml.php @@ -10,9 +10,9 @@ class Saml implements SAMLInterface { - var $settings; - var $idps = []; // contains filename -> Idp object array - var $session; // Session object + public $settings; + private $idps = []; // contains filename -> Idp object array + private $session; // Session object public function __construct(array $settings) { @@ -22,7 +22,9 @@ public function __construct(array $settings) public function loadIdpFromFile(string $filename) { - if (empty($filename)) return null; + if (empty($filename)) { + return null; + } if (array_key_exists($filename, $this->idps)) { return $this->idps[$filename]; } @@ -37,7 +39,7 @@ public function getIdpList() : array if (is_array($files)) { $mapping = array(); - foreach($files as $filename) { + foreach ($files as $filename) { $idp = $this->loadIdpFromFile($filename); $mapping[basename($filename, ".xml")] = $idp->metadata['idpEntityId']; @@ -54,6 +56,11 @@ public function getIdp(string $filename) public function getSPMetadata(): string { + if (!is_readable($this->settings['sp_cert_file'])) { + return <<Your SP certificate file is not readable. Please check file permissions. +XML; + } $entityID = $this->settings['sp_entityid']; $id = preg_replace('/[^a-z0-9_-]/', '_', $entityID); $cert = Settings::cleanOpenSsl($this->settings['sp_cert_file']); @@ -72,7 +79,7 @@ public function getSPMetadata(): string XML; foreach ($sloLocationArray as $slo) { - $location = $slo[0]; + $location = $slo[0]; $binding = $slo[1]; if (strcasecmp($binding, "POST") === 0 || strcasecmp($binding, "") === 0) { $binding = Settings::BINDING_POST; @@ -127,8 +134,14 @@ public function getSPMetadata(): string return SignatureUtils::signXml($xml, $this->settings); } - public function login(string $idpName, int $assertId, int $attrId, $level = 1, string $redirectTo = null, $shouldRedirect = true) - { + public function login( + string $idpName, + int $assertId, + int $attrId, + $level = 1, + string $redirectTo = null, + $shouldRedirect = true + ) { $args = func_get_args(); return $this->baseLogin(Settings::BINDING_REDIRECT, ...$args); } @@ -139,8 +152,15 @@ public function loginPost(string $idpName, int $assertId, int $attrId, $level = return $this->baseLogin(Settings::BINDING_POST, ...$args); } - private function baseLogin($binding = Settings::BINDING_REDIRECT, $idpName, $assertId, $attrId, $level = 1, $redirectTo = null, $shouldRedirect = true) - { + private function baseLogin( + $binding, + $idpName, + $assertId, + $attrId, + $level = 1, + $redirectTo = null, + $shouldRedirect = true + ) { if ($this->isAuthenticated()) { return false; } @@ -162,7 +182,9 @@ private function baseLogin($binding = Settings::BINDING_REDIRECT, $idpName, $ass public function isAuthenticated() : bool { $selectedIdp = $_SESSION['idpName'] ?? $_SESSION['spidSession']->idp ?? null; - if (is_null($selectedIdp)) return false; + if (is_null($selectedIdp)) { + return false; + } $idp = $this->loadIdpFromFile($_SESSION['idpName'] ?? $_SESSION['spidSession']->idp); $response = new BaseResponse($this); if (!empty($idp) && !$response->validate($idp->metadata['idpCertValue'])) { @@ -191,7 +213,7 @@ public function logoutPost(int $slo, string $redirectTo = null, $shouldRedirect return $this->baseLogout(Settings::BINDING_POST, ...$args); } - private function baseLogout($binding = Settings::BINDING_REDIRECT, $slo, $redirectTo = null, $shouldRedirect = true) + private function baseLogout($binding, $slo, $redirectTo = null, $shouldRedirect = true) { if (!$this->isAuthenticated()) { return false; @@ -202,7 +224,54 @@ private function baseLogout($binding = Settings::BINDING_REDIRECT, $slo, $redire public function getAttributes() : array { - if ($this->isAuthenticated() === false) return array(); + if ($this->isAuthenticated() === false) { + return array(); + } return $this->session->attributes; } + + public function isConfigured() : bool + { + if (!is_readable($this->settings['sp_key_file'])) { + return false; + } + if (!is_readable($this->settings['sp_cert_file'])) { + return false; + } + $key = file_get_contents($this->settings['sp_key_file']); + if (!openssl_get_privatekey($key)) { + return false; + } + $cert = file_get_contents($this->settings['sp_cert_file']); + if (!openssl_get_publickey($cert)) { + return false; + } + return true; + } + + public function configure(string $countryName, string $stateName, string $localityName, string $commonName, string $emailAddress) + { + $numberofdays = 3652 * 2; + $privkey = openssl_pkey_new(array( + "private_key_bits" => 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + )); + $dn = array( + "countryName" => $countryName, + "stateOrProvinceName" => $stateName, + "localityName" => $localityName, + "organizationName" => $orgName = $this->settings['sp_org_name'], + "organizationalUnitName" => $this->settings['sp_org_display_name'], + "commonName" => $commonName, + "emailAddress" => $emailAddress + ); + $csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha256')); + $myserial = (int) hexdec(bin2hex(openssl_random_pseudo_bytes(8))); + $configArgs = array("digest_alg" => "sha256"); + $sscert = openssl_csr_sign($csr, null, $privkey, $numberofdays, $configArgs, $myserial); + openssl_x509_export($sscert, $publickey); + openssl_pkey_export($privkey, $privatekey); + file_put_contents($this->settings['sp_key_file'], $privatekey); + file_put_contents($this->settings['sp_cert_file'], $publickey); + } } diff --git a/spid-php-lib/src/Spid/Saml/Idp.php b/spid-php-lib/src/Spid/Saml/Idp.php index f8fd3fc..f3304c5 100644 --- a/spid-php-lib/src/Spid/Saml/Idp.php +++ b/spid-php-lib/src/Spid/Saml/Idp.php @@ -29,7 +29,7 @@ public function loadFromXml($xmlFile) $fileName = $xmlFile; } else { $fileName = $this->sp->settings['idp_metadata_folder'] . $xmlFile . ".xml"; - } + } if (!file_exists($fileName)) { throw new \Exception("Metadata file $fileName not found", 1); } @@ -72,9 +72,10 @@ private static function formatCert($cert, $heads = true) $x509cert = str_replace(' ', '', $x509cert); if ($heads) { - $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; + $x509cert = "-----BEGIN CERTIFICATE-----\n" . + chunk_split($x509cert, 64, "\n") . + "-----END CERTIFICATE-----\n"; } - } return $x509cert; } @@ -85,7 +86,9 @@ public function authnRequest($ass, $attr, $binding, $level = 1, $redirectTo = nu $this->level = $level; $authn = new AuthnRequest($this); - $url = $binding == Settings::BINDING_REDIRECT ? $authn->redirectUrl($redirectTo) : $authn->httpPost($redirectTo); + $url = $binding == Settings::BINDING_REDIRECT ? + $authn->redirectUrl($redirectTo) : + $authn->httpPost($redirectTo); $_SESSION['RequestID'] = $authn->id; $_SESSION['idpName'] = $this->idpFileName; $_SESSION['idpEntityId'] = $this->metadata['idpEntityId']; @@ -106,7 +109,9 @@ public function logoutRequest(Session $session, $slo, $binding, $redirectTo = nu $this->session = $session; $logoutRequest = new LogoutRequest($this); - $url = ($binding == Settings::BINDING_REDIRECT) ? $logoutRequest->redirectUrl($redirectTo) : $logoutRequest->httpPost($redirectTo); + $url = ($binding == Settings::BINDING_REDIRECT) ? + $logoutRequest->redirectUrl($redirectTo) : + $logoutRequest->httpPost($redirectTo); $_SESSION['RequestID'] = $logoutRequest->id; $_SESSION['idpName'] = $this->idpFileName; @@ -129,7 +134,9 @@ public function logoutResponse() : string $this->session = $session; $logoutResponse = new LogoutResponse($this); - $url = ($binding == Settings::BINDING_REDIRECT) ? $logoutResponse->redirectUrl($redirectTo) : $logoutResponse->httpPost($redirectTo); + $url = ($binding == Settings::BINDING_REDIRECT) ? + $logoutResponse->redirectUrl($redirectTo) : + $logoutResponse->httpPost($redirectTo); unset($_SESSION); if ($binding == Settings::BINDING_POST) { diff --git a/spid-php-lib/src/Spid/Saml/In/BaseResponse.php b/spid-php-lib/src/Spid/Saml/In/BaseResponse.php index 8e61ef9..88410d0 100644 --- a/spid-php-lib/src/Spid/Saml/In/BaseResponse.php +++ b/spid-php-lib/src/Spid/Saml/In/BaseResponse.php @@ -9,7 +9,7 @@ * Generates the proper response object at runtime by reading the input XML. * Validates the response and the signature * Specific response may complete other tasks upon succesful validation -* such as creating a login session for Response, or destroying the session +* such as creating a login session for Response, or destroying the session * for Logout resposnes. * The only case in which a Request is validated instead of a response is @@ -18,18 +18,19 @@ */ class BaseResponse { - var $response; - var $xml; + private $response; + private $xml; public function __construct(Saml $saml = null) { - if ( - (!isset($_POST) || !isset($_POST['SAMLResponse'])) && + if ((!isset($_POST) || !isset($_POST['SAMLResponse'])) && (!isset($_GET) || !isset($_GET['SAMLResponse'])) ) { return; } - $xmlString = isset($_GET['SAMLResponse']) ? gzinflate(base64_decode($_GET['SAMLResponse'])): base64_decode($_POST['SAMLResponse']); + $xmlString = isset($_GET['SAMLResponse']) ? + gzinflate(base64_decode($_GET['SAMLResponse'])) : + base64_decode($_POST['SAMLResponse']); $this->xml = new \DOMDocument(); $this->xml->loadXML($xmlString); @@ -40,15 +41,21 @@ public function __construct(Saml $saml = null) case 'samlp:Response': // When reloading the acs page, POST data is sent again even if login is completed // If login session already exists exit without checking the response again - if (isset($_SESSION['spidSession'])) return; - if (is_null($saml)) return; + if (isset($_SESSION['spidSession'])) { + return; + } + if (is_null($saml)) { + return; + } $this->response = new Response($saml); break; case 'samlp:LogoutResponse': $this->response = new LogoutResponse(); break; case 'samlp:LogoutRequest': - if (is_null($saml)) return; + if (is_null($saml)) { + return; + } $this->response = new LogoutRequest($saml); break; default: @@ -66,19 +73,28 @@ public function validate($cert) : bool $hasAssertion = $this->xml->getElementsByTagName('Assertion')->length > 0; $signatures = $this->xml->getElementsByTagName('Signature'); - if ($hasAssertion && $signatures->length == 0) throw new \Exception("Invalid Response. Response must contain at least one signature"); + if ($hasAssertion && $signatures->length == 0) { + throw new \Exception("Invalid Response. Response must contain at least one signature"); + } $responseSignature = null; $assertionSignature = null; if ($signatures->length > 0) { foreach ($signatures as $key => $item) { - if ($item->parentNode->nodeName == 'saml:Assertion') $assertionSignature = $item; - if ($item->parentNode->nodeName == $this->xml->firstChild->nodeName) $responseSignature = $item; + if ($item->parentNode->nodeName == 'saml:Assertion') { + $assertionSignature = $item; + } + if ($item->parentNode->nodeName == $this->xml->firstChild->nodeName) { + $responseSignature = $item; + } + } + if ($hasAssertion && is_null($assertionSignature)) { + throw new \Exception("Invalid Response. Assertion must be signed"); } - if ($hasAssertion && is_null($assertionSignature)) throw new \Exception("Invalid Response. Assertion must be signed"); } - if (!SignatureUtils::validateXmlSignature($responseSignature, $cert) || !SignatureUtils::validateXmlSignature($assertionSignature, $cert)) + if (!SignatureUtils::validateXmlSignature($responseSignature, $cert) || !SignatureUtils::validateXmlSignature($assertionSignature, $cert)) { throw new \Exception("Invalid Response. Signature validation failed"); + } return $this->response->validate($this->xml, $hasAssertion); } } diff --git a/spid-php-lib/src/Spid/Saml/In/LogoutRequest.php b/spid-php-lib/src/Spid/Saml/In/LogoutRequest.php index 5cc3e0f..5c66fe3 100644 --- a/spid-php-lib/src/Spid/Saml/In/LogoutRequest.php +++ b/spid-php-lib/src/Spid/Saml/In/LogoutRequest.php @@ -10,7 +10,7 @@ class LogoutRequest implements ResponseInterface private $saml; - public function __construct(Saml $saml) + public function __construct(Saml $saml) { $this->saml = $saml; } @@ -19,9 +19,15 @@ public function validate($xml, $hasAssertion) : bool { $root = $xml->getElementsByTagName('LogoutRequest')->item(0); - if ($xml->getElementsByTagName('Issuer')->length == 0) throw new \Exception("Invalid Response. Missing Issuer element"); - if ($xml->getElementsByTagName('NameID')->length == 0) throw new \Exception("Invalid Response. Missing NameID element"); - if ($xml->getElementsByTagName('SessionIndex')->length == 0) throw new \Exception("Invalid Response. Missing SessionIndex element"); + if ($xml->getElementsByTagName('Issuer')->length == 0) { + throw new \Exception("Invalid Response. Missing Issuer element"); + } + if ($xml->getElementsByTagName('NameID')->length == 0) { + throw new \Exception("Invalid Response. Missing NameID element"); + } + if ($xml->getElementsByTagName('SessionIndex')->length == 0) { + throw new \Exception("Invalid Response. Missing SessionIndex element"); + } if ($issuer->getAttribute('Destination') == "") { throw new \Exception("Missing Destination attribute"); @@ -60,4 +66,4 @@ public function validate($xml, $hasAssertion) : bool $_SESSION['inResponseTo'] = $root->getAttribute('ID'); return true; } -} \ No newline at end of file +} diff --git a/spid-php-lib/src/Spid/Saml/In/LogoutResponse.php b/spid-php-lib/src/Spid/Saml/In/LogoutResponse.php index 80e099a..0963a3b 100644 --- a/spid-php-lib/src/Spid/Saml/In/LogoutResponse.php +++ b/spid-php-lib/src/Spid/Saml/In/LogoutResponse.php @@ -45,4 +45,4 @@ public function validate($xml, $hasAssertion) : bool session_unset(); return true; } -} \ No newline at end of file +} diff --git a/spid-php-lib/src/Spid/Saml/In/Response.php b/spid-php-lib/src/Spid/Saml/In/Response.php index d029967..6a09bfd 100644 --- a/spid-php-lib/src/Spid/Saml/In/Response.php +++ b/spid-php-lib/src/Spid/Saml/In/Response.php @@ -11,7 +11,7 @@ class Response implements ResponseInterface private $saml; - public function __construct(Saml $saml) + public function __construct(Saml $saml) { $this->saml = $saml; } @@ -43,7 +43,7 @@ public function validate($xml, $hasAssertion): bool //check item 0, this the Issuer element child of Response } elseif ($xml->getElementsByTagName('Issuer')->item(0)->nodeValue != $_SESSION['idpEntityId']) { throw new \Exception("Invalid Issuer attribute, expected " . $_SESSION['idpEntityId'] . " but received " . $xml->getElementsByTagName('Issuer')->item(0)->nodeValue); - } + } if ($hasAssertion) { //check item 1, this the Issuer element child of Assertion if ($hasAssertion && $xml->getElementsByTagName('Issuer')->item(1)->nodeValue != $_SESSION['idpEntityId']) { diff --git a/spid-php-lib/src/Spid/Saml/Out/Base.php b/spid-php-lib/src/Spid/Saml/Out/Base.php index b4a0798..7a91b54 100644 --- a/spid-php-lib/src/Spid/Saml/Out/Base.php +++ b/spid-php-lib/src/Spid/Saml/Out/Base.php @@ -64,9 +64,13 @@ protected function getBindingLocation($binding, $service = 'SSO') $location = null; $key = 'idp' . $service; array_walk($this->idp->metadata[$key], function ($val) use ($binding, &$location) { - if ($binding == $val['binding']) $location = $val['location']; + if ($binding == $val['binding']) { + $location = $val['location']; + } }); - if (is_null($location)) throw new \Exception("No location found for binding " . $binding); + if (is_null($location)) { + throw new \Exception("No location found for binding " . $binding); + } return $location; } } diff --git a/spid-php-lib/src/Spid/Saml/Settings.php b/spid-php-lib/src/Spid/Saml/Settings.php index 9e70508..a930178 100644 --- a/spid-php-lib/src/Spid/Saml/Settings.php +++ b/spid-php-lib/src/Spid/Saml/Settings.php @@ -73,6 +73,9 @@ public static function cleanOpenSsl($file, $isCert = false) if ($isCert) { $k = $file; } else { + if (!is_readable($file)) { + throw new \Exception('File '.$file.' is not readable. Please check file permissions.'); + } $k = file_get_contents($file); } $ck = ''; @@ -86,35 +89,63 @@ public static function cleanOpenSsl($file, $isCert = false) private static function checkSettingsValues($settings) { - if (filter_var($settings['sp_entityid'], FILTER_VALIDATE_URL) === false) + if (filter_var($settings['sp_entityid'], FILTER_VALIDATE_URL) === false) { throw new \Exception('Invalid SP Entity ID provided'); + } // Save entity id host url for other checks $host = parse_url($settings['sp_entityid'], PHP_URL_HOST); if (isset($settings['sp_attributeconsumingservice'])) { - if (!is_array($settings['sp_attributeconsumingservice'])) throw new \Exception('sp_attributeconsumingservice should be an array'); - array_walk($settings['sp_attributeconsumingservice'], function($acs) { - if (!is_array($acs)) throw new \Exception('sp_attributeconsumingservice elements should be an arrays'); - array_walk($acs, function($field) { - if (!in_array($field, self::$validAttributeFields)) throw new \Exception('Invalid Attribute field '. $field .' requested'); + if (!is_array($settings['sp_attributeconsumingservice'])) { + throw new \Exception('sp_attributeconsumingservice should be an array'); + } + array_walk($settings['sp_attributeconsumingservice'], function ($acs) { + if (!is_array($acs)) { + throw new \Exception('sp_attributeconsumingservice elements should be an arrays'); + } + array_walk($acs, function ($field) { + if (!in_array($field, self::$validAttributeFields)) { + throw new \Exception('Invalid Attribute field '. $field .' requested'); + } }); - }); + }); } - if (!is_array($settings['sp_assertionconsumerservice'])) throw new \Exception('sp_assertionconsumerservice should be an array'); - array_walk($settings['sp_assertionconsumerservice'], function($acs) use ($host){ - if (strpos($acs, $host) === false) throw new \Exception('sp_assertionconsumerservice elements Location domain should be ' . $host . ', got ' . parse_url($acs, PHP_URL_HOST) . 'instead'); + if (!is_array($settings['sp_assertionconsumerservice'])) { + throw new \Exception('sp_assertionconsumerservice should be an array'); + } + array_walk($settings['sp_assertionconsumerservice'], function ($acs) use ($host) { + if (strpos($acs, $host) === false) { + throw new \Exception('sp_assertionconsumerservice elements Location domain should be ' . $host . + ', got ' . parse_url($acs, PHP_URL_HOST) . 'instead'); + } }); - if (!is_array($settings['sp_singlelogoutservice'])) throw new \Exception('sp_singlelogoutservice should be an array'); - array_walk($settings['sp_singlelogoutservice'], function($slo) use($host) { - if (!is_array($slo)) throw new \Exception('sp_singlelogoutservice elements should be arrays'); - if (count($slo) != 2) throw new \Exception('sp_singlelogoutservice array elements should contain exactly 2 elements, in order SLO Location and Binding'); - if (!is_string($slo[0]) || !is_string($slo[1])) throw new \Exception('sp_singlelogoutservice array elements should contain 2 string values, in order SLO Location and Binding'); - if (strcasecmp($slo[1], "POST") != 0 && strcasecmp($slo[1], "REDIRECT") != 0 && strcasecmp($slo[1], "") != 0) { - throw new \Exception('sp_singlelogoutservice elements Binding value should be one between "POST", "REDIRECT", or "" (empty string, defaults to POST)'); + if (!is_array($settings['sp_singlelogoutservice'])) { + throw new \Exception('sp_singlelogoutservice should be an array'); + } + array_walk($settings['sp_singlelogoutservice'], function ($slo) use ($host) { + if (!is_array($slo)) { + throw new \Exception('sp_singlelogoutservice elements should be arrays'); + } + if (count($slo) != 2) { + throw new \Exception('sp_singlelogoutservice array elements should contain exactly 2 elements,\ + in order SLO Location and Binding'); + } + if (!is_string($slo[0]) || !is_string($slo[1])) { + throw new \Exception('sp_singlelogoutservice array elements should contain 2 string values,\ + in order SLO Location and Binding'); + } + if (strcasecmp($slo[1], "POST") != 0 && + strcasecmp($slo[1], "REDIRECT") != 0 && + strcasecmp($slo[1], "") != 0) { + throw new \Exception('sp_singlelogoutservice elements Binding value should be one of\ + "POST", "REDIRECT", or "" (empty string, defaults to POST)'); + } + if (strpos($slo[0], $host) === false) { + throw new \Exception('sp_singlelogoutservice elements Location domain should be ' . $host . + ', got ' . parse_url($slo[0], PHP_URL_HOST) . 'instead'); } - if (strpos($slo[0], $host) === false) throw new \Exception('sp_singlelogoutservice elements Location domain should be ' . $host . ', got ' . parse_url($slo[0], PHP_URL_HOST) . 'instead'); }); } } diff --git a/spid-php-lib/src/Spid/Saml/SignatureUtils.php b/spid-php-lib/src/Spid/Saml/SignatureUtils.php index ae8ad4f..1c661ae 100644 --- a/spid-php-lib/src/Spid/Saml/SignatureUtils.php +++ b/spid-php-lib/src/Spid/Saml/SignatureUtils.php @@ -10,6 +10,12 @@ class SignatureUtils { public static function signXml($xml, $settings) : string { + if (!is_readable($settings['sp_key_file'])) { + throw new \Exception('Your SP key file is not readable. Please check file permissions.'); + } + if (!is_readable($settings['sp_cert_file'])) { + throw new \Exception('Your SP certificate file is not readable. Please check file permissions.'); + } $key = file_get_contents($settings['sp_key_file']); $key = openssl_get_privatekey($key, ""); $cert = file_get_contents($settings['sp_cert_file']); @@ -46,6 +52,9 @@ public static function signXml($xml, $settings) : string public static function signUrl($samlRequest, $relayState, $signatureAlgo, $keyFile) { + if (!is_readable($keyFile)) { + throw new \Exception('Your SP key file is not readable. Please check file permissions.'); + } $key = file_get_contents($keyFile); $key = openssl_get_privatekey($key, ""); @@ -61,11 +70,16 @@ public static function signUrl($samlRequest, $relayState, $signatureAlgo, $keyFi public static function validateXmlSignature($xml, $cert) : bool { - if (is_null($xml)) return true; + if (is_null($xml)) { + return true; + } $dom = clone $xml->ownerDocument; $certFingerprint = Settings::cleanOpenSsl($cert, true); - $signCertFingerprint = Settings::cleanOpenSsl($dom->getElementsByTagName('X509Certificate')->item(0)->nodeValue, true); + $signCertFingerprint = Settings::cleanOpenSsl( + $dom->getElementsByTagName('X509Certificate')->item(0)->nodeValue, + true + ); if ($signCertFingerprint != $certFingerprint) { return false; } diff --git a/spid-php-lib/vendor/autoload.php b/spid-php-lib/vendor/autoload.php new file mode 100644 index 0000000..b81ce34 --- /dev/null +++ b/spid-php-lib/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/spid-php-lib/vendor/composer/LICENSE b/spid-php-lib/vendor/composer/LICENSE new file mode 100644 index 0000000..f0157a6 --- /dev/null +++ b/spid-php-lib/vendor/composer/LICENSE @@ -0,0 +1,56 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: Composer +Upstream-Contact: Jordi Boggiano +Source: https://github.com/composer/composer + +Files: * +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano +License: Expat + +Files: src/Composer/Util/TlsHelper.php +Copyright: 2016, Nils Adermann + 2016, Jordi Boggiano + 2013, Evan Coury +License: Expat and BSD-2-Clause + +License: BSD-2-Clause + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + . + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + . + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + . + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +License: Expat + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is furnished + to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/spid-php-lib/vendor/composer/autoload_classmap.php b/spid-php-lib/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/spid-php-lib/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/robrichards/xmlseclibs/src'), + 'Italia\\Spid\\' => array($baseDir . '/src'), +); diff --git a/spid-php-lib/vendor/composer/autoload_real.php b/spid-php-lib/vendor/composer/autoload_real.php new file mode 100644 index 0000000..a78c248 --- /dev/null +++ b/spid-php-lib/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit52b797d1e367a882360c3e1a6266bfad::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/spid-php-lib/vendor/composer/autoload_static.php b/spid-php-lib/vendor/composer/autoload_static.php new file mode 100644 index 0000000..5f636fc --- /dev/null +++ b/spid-php-lib/vendor/composer/autoload_static.php @@ -0,0 +1,39 @@ + + array ( + 'RobRichards\\XMLSecLibs\\' => 23, + ), + 'I' => + array ( + 'Italia\\Spid\\' => 12, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'RobRichards\\XMLSecLibs\\' => + array ( + 0 => __DIR__ . '/..' . '/robrichards/xmlseclibs/src', + ), + 'Italia\\Spid\\' => + array ( + 0 => __DIR__ . '/../..' . '/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit52b797d1e367a882360c3e1a6266bfad::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit52b797d1e367a882360c3e1a6266bfad::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/spid-php-lib/vendor/composer/installed.json b/spid-php-lib/vendor/composer/installed.json new file mode 100644 index 0000000..a5e33a9 --- /dev/null +++ b/spid-php-lib/vendor/composer/installed.json @@ -0,0 +1,42 @@ +[ + { + "name": "robrichards/xmlseclibs", + "version": "3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/robrichards/xmlseclibs.git", + "reference": "1f4de0c0d121922aafd8c62c2a9f5e528830cf38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/1f4de0c0d121922aafd8c62c2a9f5e528830cf38", + "reference": "1f4de0c0d121922aafd8c62c2a9f5e528830cf38", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "php": ">= 5.4" + }, + "time": "2018-09-27T13:24:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "A PHP library for XML Security", + "homepage": "https://github.com/robrichards/xmlseclibs", + "keywords": [ + "security", + "signature", + "xml", + "xmldsig" + ] + } +] diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/CHANGELOG.txt b/spid-php-lib/vendor/robrichards/xmlseclibs/CHANGELOG.txt new file mode 100644 index 0000000..62ea9cd --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/CHANGELOG.txt @@ -0,0 +1,191 @@ +xmlseclibs.php +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| +27, Sep 2018, 3.0.2 +Security Improvements: +- OpenSSL is now a requirement rather than suggestion. (Slaven Bacelic) +- Filter input to avoid XPath injection. (Jaime Pérez) + +Bug Fixes: +- Fix missing parentheses (Tim van Dijen) + +Improvements: +- Use strict comparison operator to compare digest values. (Jaime Pérez) +- Remove call to file_get_contents that doesn't even work. (Jaime Pérez) +- Document potentially dangerous return value behaviour. (Thijs Kinkhorst) + +31, Aug 2017, 3.0.1 +Bug Fixes: +- Fixed missing () in function call. (Dennis Væversted) + +Improvements: +- Add OneLogin to supported software. +- Add .gitattributes to remove unneeded files. (Filippo Tessarotto) +- Fix bug in example code. (Dan Church) +- Travis: add PHP 7.1, move hhvm to allowed failures. (Thijs Kinkhorst) +- Drop failing extract-win-cert test (Thijs Kinkhorst). (Thijs Kinkhorst) +- Add comments to warn about return values of verify(). (Thijs Kinkhorst) +- Fix tests to properly check return code of verify(). (Thijs Kinkhorst) +- Restore support for PHP >= 5.4. (Jaime Pérez) + +25, May 2017, 3.0.0 +Improvements: +- Remove use of mcrypt (skymeyer) + +08, Sep 2016, 2.0.1 +Bug Fixes: +- Strip whitespace characters when parsing X509Certificate. fixes #84 + (klemen.bratec) +- Certificate 'subject' values can be arrays. fixes #80 (Andreas Stangl) +- HHVM signing node with ID attribute w/out namespace regenerates ID value. + fixes #88 (Milos Tomic) + +Improvements: +- Fix typos and add some PHPDoc Blocks. (gfaust-qb) +- Update lightSAML link. (Milos Tomic) +- Update copyright dates. + +31, Jul 2015, 2.0.0 +Features: +- Namespace support. Classes now in the RobRichards\XMLSecLibs\ namespace. + +Improvements: +- Dropped support for PHP 5.2 + +31, Jul 2015, 1.4.1 +Bug Fixes: +- Allow for large digest values that may have line breaks. fixes #62 + +Features: +- Support for locating specific signature when multiple exist in + document. (griga3k) + +Improvements: +- Add optional argument to XMLSecurityDSig to define the prefix to be used, + also allowing for null to use no prefix, for the dsig namespace. fixes #13 +- Code cleanup +- Depreciated XMLSecurityDSig::generate_GUID for XMLSecurityDSig::generateGUID + +23, Jun 2015, 1.4.0 +Features: +- Support for PSR-0 standard. +- Support for X509SubjectName. (Milos Tomic) +- Add HMAC-SHA1 support. + +Improvements: +- Add how to install to README. (Bernardo Vieira da Silva) +- Code cleanup. (Jaime Pérez) +- Normalilze tests. (Hidde Wieringa) +- Add basic usage to README. (Hidde Wieringa) + +21, May 2015, 1.3.2 +Bug Fixes: +- Fix Undefined variable notice. (dpieper85) +- Fix typo when setting MimeType attribute. (Eugene OZ) +- Fix validateReference() with enveloping signatures + +Features: +- canonicalizeData performance optimization. (Jaime Pérez) +- Add composer support (Maks3w) + +19, Jun 2013, 1.3.1 +Features: +- return encrypted node from XMLSecEnc::encryptNode() when replace is set to + false. (Olav) +- Add support for RSA SHA384 and RSA_SHA512 and SHA384 digest. (Jaime PŽrez) +- Add options parameter to the add cert methods. +- Add optional issuerSerial creation with cert + +Bug Fixes: +- Fix persisted Id when namespaced. (Koen Thomeer) + +Improvements: +- Add LICENSE file +- Convert CHANGELOG.txt to UTF-8 + +26, Sep 2011, 1.3.0 +Features: +- Add param to append sig to node when signing. Fixes a problem when using + inclusive canonicalization to append a signature within a namespaced subtree. + ex. $objDSig->sign($objKey, $appendToNode); +- Add ability to encrypt by reference +- Add support for refences within an encrypted key +- Add thumbprint generation capability (XMLSecurityKey->getX509Thumbprint() and + XMLSecurityKey::getRawThumbprint($cert)) +- Return signature element node from XMLSecurityDSig::insertSignature() and + XMLSecurityDSig::appendSignature() methods +- Support for with simple URI Id reference. +- Add XMLSecurityKey::getSymmetricKeySize() method (Olav) +- Add XMLSecEnc::getCipherValue() method (Olav) +- Improve XMLSecurityKey:generateSessionKey() logic (Olav) + +Bug Fixes: +- Change split() to explode() as split is now depreciated +- ds:References using empty or simple URI Id reference should never include + comments in canonicalized data. +- Make sure that the elements in EncryptedData are emitted in the correct + sequence. + +11 Jan 2010, 1.2.2 +Features: +- Add support XPath support when creating signature. Provides support for + working with EBXML documents. +- Add reference option to force creation of URI attribute. For use + when adding a DOM Document where by default no URI attribute is added. +- Add support for RSA-SHA256 + +Bug Fixes: +- fix bug #5: createDOMDocumentFragment() in decryptNode when data is node + content (patch by Francois Wang) + + +08 Jul 2008, 1.2.1 +Features: +- Attempt to use mhash when hash extension is not present. (Alfredo Cubitos). +- Add fallback to built-in sha1 if both hash and mhash are not available and + throw error for other for other missing hashes. (patch by Olav Morken). +- Add getX509Certificate method to retrieve the x509 cert used for Key. + (patch by Olav Morken). +- Add getValidatedNodes method to retrieve the elements signed by the + signature. (patch by Olav Morken). +- Add insertSignature method for precision signature insertion. Merge + functionality from appendSignature in the process. (Olav Morken, Rob). +- Finally add some tests + +Bug Fixes: +- Fix canonicalization for Document node when using PHP < 5.2. +- Add padding for RSA_SHA1. (patch by Olav Morken). + + +27 Nov 2007, 1.2.0 +Features: +- New addReference/List option (overwrite). Boolean flag indicating if URI + value should be overwritten if already existing within document. + Default is TRUE to maintain BC. + +18 Nov 2007, 1.1.2 +Bug Fixes: +- Remove closing PHP tag to fix extra whitespace characters from being output + +11 Nov 2007, 1.1.1 +Features: +- Add getRefNodeID() and getRefIDs() methods missed in previous release. + Provide functionality to find URIs of existing reference nodes. + Required by simpleSAMLphp project + +Bug Fixes: +- Remove erroneous whitespace causing issues under certain circumastances. + +18 Oct 2007, 1.1.0 +Features: +- Enable creation of enveloping signature. This allows the creation of + managed information cards. +- Add addObject method for enveloping signatures. +- Add staticGet509XCerts method. Chained certificates within a PEM file can + now be added within the X509Data node. +- Add xpath support within transformations +- Add InclusiveNamespaces prefix list support within exclusive transformations. + +Bug Fixes: +- Initialize random number generator for mcrypt_create_iv. (Joan Cornadó). +- Fix an interoperability issue with .NET when encrypting data in CBC mode. + (Joan Cornadó). diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/LICENSE b/spid-php-lib/vendor/robrichards/xmlseclibs/LICENSE new file mode 100644 index 0000000..54b1e87 --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/LICENSE @@ -0,0 +1,31 @@ +Copyright (c) 2007-2018, Robert Richards . +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Robert Richards nor the names of his + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/README.md b/spid-php-lib/vendor/robrichards/xmlseclibs/README.md new file mode 100644 index 0000000..e8ebd02 --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/README.md @@ -0,0 +1,84 @@ +#xmlseclibs + +xmlseclibs is a library written in PHP for working with XML Encryption and Signatures. + +The author of xmlseclibs is Rob Richards. + +# Branches +Both the master and the 2.0 branches are actively maintained. +* master: Removes mcrypt usage requiring 5.4+ (5.6.24+ recommended for security reasons) +* 2.0: Contains namespace support requiring 5.3+ +* 1.4: Contains auto-loader support while also maintaining backwards compatiblity with the older 1.3 version using the xmlseclibs.php file. Supports PHP 5.2+ + +# Requirements + +xmlseclibs requires PHP version 5.4 or greater. **5.6.24+ recommended for security reasons** + + +## How to Install + +Install with [`composer.phar`](http://getcomposer.org). + +```sh +php composer.phar require "robrichards/xmlseclibs" +``` + + +## Use cases + +xmlseclibs is being used in many different software. + +* [SimpleSAMLPHP](https://github.com/simplesamlphp/simplesamlphp) +* [LightSAML](https://github.com/lightsaml/lightsaml) +* [OneLogin](https://github.com/onelogin/php-saml) + +## Basic usage + +The example below shows basic usage of xmlseclibs, with a SHA-256 signature. + +```php +use RobRichards\XMLSecLibs\XMLSecurityDSig; +use RobRichards\XMLSecLibs\XMLSecurityKey; + +// Load the XML to be signed +$doc = new DOMDocument(); +$doc->load('./path/to/file/tobesigned.xml'); + +// Create a new Security object +$objDSig = new XMLSecurityDSig(); +// Use the c14n exclusive canonicalization +$objDSig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N); +// Sign using SHA-256 +$objDSig->addReference( + $doc, + XMLSecurityDSig::SHA256, + array('http://www.w3.org/2000/09/xmldsig#enveloped-signature') +); + +// Create a new (private) Security key +$objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, array('type'=>'private')); +/* +If key has a passphrase, set it using +$objKey->passphrase = ''; +*/ +// Load the private key +$objKey->loadKey('./path/to/privatekey.pem', TRUE); + +// Sign the XML file +$objDSig->sign($objKey); + +// Add the associated public key to the signature +$objDSig->add509Cert(file_get_contents('./path/to/file/mycert.pem')); + +// Append the signature to the XML +$objDSig->appendSignature($doc->documentElement); +// Save the signed XML +$doc->save('./path/to/signed.xml'); +``` + +## How to Contribute + +* [Open Issues](https://github.com/robrichards/xmlseclibs/issues) +* [Open Pull Requests](https://github.com/robrichards/xmlseclibs/pulls) + +Mailing List: https://groups.google.com/forum/#!forum/xmlseclibs diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/composer.json b/spid-php-lib/vendor/robrichards/xmlseclibs/composer.json new file mode 100644 index 0000000..22ce7a3 --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/composer.json @@ -0,0 +1,21 @@ +{ + "name": "robrichards/xmlseclibs", + "description": "A PHP library for XML Security", + "license": "BSD-3-Clause", + "keywords": [ + "xml", + "xmldsig", + "signature", + "security" + ], + "homepage": "https://github.com/robrichards/xmlseclibs", + "autoload": { + "psr-4": { + "RobRichards\\XMLSecLibs\\": "src" + } + }, + "require": { + "php": ">= 5.4", + "ext-openssl": "*" + } +} diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/src/Utils/XPath.php b/spid-php-lib/vendor/robrichards/xmlseclibs/src/Utils/XPath.php new file mode 100644 index 0000000..11e51fb --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/src/Utils/XPath.php @@ -0,0 +1,44 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecEnc +{ + const template = " + + + +"; + + const Element = 'http://www.w3.org/2001/04/xmlenc#Element'; + const Content = 'http://www.w3.org/2001/04/xmlenc#Content'; + const URI = 3; + const XMLENCNS = 'http://www.w3.org/2001/04/xmlenc#'; + + /** @var null|DOMDocument */ + private $encdoc = null; + + /** @var null|DOMNode */ + private $rawNode = null; + + /** @var null|string */ + public $type = null; + + /** @var null|DOMElement */ + public $encKey = null; + + /** @var array */ + private $references = array(); + + public function __construct() + { + $this->_resetTemplate(); + } + + private function _resetTemplate() + { + $this->encdoc = new DOMDocument(); + $this->encdoc->loadXML(self::template); + } + + /** + * @param string $name + * @param DOMNode $node + * @param string $type + * @throws Exception + */ + public function addReference($name, $node, $type) + { + if (! $node instanceOf DOMNode) { + throw new Exception('$node is not of type DOMNode'); + } + $curencdoc = $this->encdoc; + $this->_resetTemplate(); + $encdoc = $this->encdoc; + $this->encdoc = $curencdoc; + $refuri = XMLSecurityDSig::generateGUID(); + $element = $encdoc->documentElement; + $element->setAttribute("Id", $refuri); + $this->references[$name] = array("node" => $node, "type" => $type, "encnode" => $encdoc, "refuri" => $refuri); + } + + /** + * @param DOMNode $node + */ + public function setNode($node) + { + $this->rawNode = $node; + } + + /** + * Encrypt the selected node with the given key. + * + * @param XMLSecurityKey $objKey The encryption key and algorithm. + * @param bool $replace Whether the encrypted node should be replaced in the original tree. Default is true. + * @throws Exception + * + * @return DOMElement The -element. + */ + public function encryptNode($objKey, $replace = true) + { + $data = ''; + if (empty($this->rawNode)) { + throw new Exception('Node to encrypt has not been set'); + } + if (! $objKey instanceof XMLSecurityKey) { + throw new Exception('Invalid Key'); + } + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($this->encdoc); + $objList = $xPath->query('/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue'); + $cipherValue = $objList->item(0); + if ($cipherValue == null) { + throw new Exception('Error locating CipherValue element within template'); + } + switch ($this->type) { + case (self::Element): + $data = $doc->saveXML($this->rawNode); + $this->encdoc->documentElement->setAttribute('Type', self::Element); + break; + case (self::Content): + $children = $this->rawNode->childNodes; + foreach ($children AS $child) { + $data .= $doc->saveXML($child); + } + $this->encdoc->documentElement->setAttribute('Type', self::Content); + break; + default: + throw new Exception('Type is currently not supported'); + } + + $encMethod = $this->encdoc->documentElement->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $objKey->getAlgorithm()); + $cipherValue->parentNode->parentNode->insertBefore($encMethod, $cipherValue->parentNode->parentNode->firstChild); + + $strEncrypt = base64_encode($objKey->encryptData($data)); + $value = $this->encdoc->createTextNode($strEncrypt); + $cipherValue->appendChild($value); + + if ($replace) { + switch ($this->type) { + case (self::Element): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $this->encdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + $importEnc = $this->rawNode->ownerDocument->importNode($this->encdoc->documentElement, true); + while ($this->rawNode->firstChild) { + $this->rawNode->removeChild($this->rawNode->firstChild); + } + $this->rawNode->appendChild($importEnc); + return $importEnc; + } + } else { + return $this->encdoc->documentElement; + } + } + + /** + * @param XMLSecurityKey $objKey + * @throws Exception + */ + public function encryptReferences($objKey) + { + $curRawNode = $this->rawNode; + $curType = $this->type; + foreach ($this->references AS $name => $reference) { + $this->encdoc = $reference["encnode"]; + $this->rawNode = $reference["node"]; + $this->type = $reference["type"]; + try { + $encNode = $this->encryptNode($objKey); + $this->references[$name]["encnode"] = $encNode; + } catch (Exception $e) { + $this->rawNode = $curRawNode; + $this->type = $curType; + throw $e; + } + } + $this->rawNode = $curRawNode; + $this->type = $curType; + } + + /** + * Retrieve the CipherValue text from this encrypted node. + * + * @throws Exception + * @return string|null The Ciphervalue text, or null if no CipherValue is found. + */ + public function getCipherValue() + { + if (empty($this->rawNode)) { + throw new Exception('Node to decrypt has not been set'); + } + + $doc = $this->rawNode->ownerDocument; + $xPath = new DOMXPath($doc); + $xPath->registerNamespace('xmlencr', self::XMLENCNS); + /* Only handles embedded content right now and not a reference */ + $query = "./xmlencr:CipherData/xmlencr:CipherValue"; + $nodeset = $xPath->query($query, $this->rawNode); + $node = $nodeset->item(0); + + if (!$node) { + return null; + } + + return base64_decode($node->nodeValue); + } + + /** + * Decrypt this encrypted node. + * + * The behaviour of this function depends on the value of $replace. + * If $replace is false, we will return the decrypted data as a string. + * If $replace is true, we will insert the decrypted element(s) into the + * document, and return the decrypted element(s). + * + * @param XMLSecurityKey $objKey The decryption key that should be used when decrypting the node. + * @param 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'); + } + + $encryptedData = $this->getCipherValue(); + if ($encryptedData) { + $decrypted = $objKey->decryptData($encryptedData); + if ($replace) { + switch ($this->type) { + case (self::Element): + $newdoc = new DOMDocument(); + $newdoc->loadXML($decrypted); + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + return $newdoc; + } + $importEnc = $this->rawNode->ownerDocument->importNode($newdoc->documentElement, true); + $this->rawNode->parentNode->replaceChild($importEnc, $this->rawNode); + return $importEnc; + case (self::Content): + if ($this->rawNode->nodeType == XML_DOCUMENT_NODE) { + $doc = $this->rawNode; + } else { + $doc = $this->rawNode->ownerDocument; + } + $newFrag = $doc->createDocumentFragment(); + $newFrag->appendXML($decrypted); + $parent = $this->rawNode->parentNode; + $parent->replaceChild($newFrag, $this->rawNode); + return $parent; + default: + return $decrypted; + } + } else { + return $decrypted; + } + } else { + throw new Exception("Cannot locate encrypted data"); + } + } + + /** + * Encrypt the XMLSecurityKey + * + * @param XMLSecurityKey $srcKey + * @param XMLSecurityKey $rawKey + * @param bool $append + * @throws Exception + */ + public function encryptKey($srcKey, $rawKey, $append=true) + { + if ((! $srcKey instanceof XMLSecurityKey) || (! $rawKey instanceof XMLSecurityKey)) { + throw new Exception('Invalid Key'); + } + $strEncKey = base64_encode($srcKey->encryptData($rawKey->key)); + $root = $this->encdoc->documentElement; + $encKey = $this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptedKey'); + if ($append) { + $keyInfo = $root->insertBefore($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo'), $root->firstChild); + $keyInfo->appendChild($encKey); + } else { + $this->encKey = $encKey; + } + $encMethod = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:EncryptionMethod')); + $encMethod->setAttribute('Algorithm', $srcKey->getAlgorith()); + if (! empty($srcKey->name)) { + $keyInfo = $encKey->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyInfo')); + $keyInfo->appendChild($this->encdoc->createElementNS('http://www.w3.org/2000/09/xmldsig#', 'dsig:KeyName', $srcKey->name)); + } + $cipherData = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherData')); + $cipherData->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:CipherValue', $strEncKey)); + if (is_array($this->references) && count($this->references) > 0) { + $refList = $encKey->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:ReferenceList')); + foreach ($this->references AS $name => $reference) { + $refuri = $reference["refuri"]; + $dataRef = $refList->appendChild($this->encdoc->createElementNS(self::XMLENCNS, 'xenc:DataReference')); + $dataRef->setAttribute("URI", '#' . $refuri); + } + } + return; + } + + /** + * @param XMLSecurityKey $encKey + * @return DOMElement|string + * @throws Exception + */ + public function decryptKey($encKey) + { + if (! $encKey->isEncrypted) { + throw new Exception("Key is not Encrypted"); + } + if (empty($encKey->key)) { + throw new Exception("Key is missing data to perform the decryption"); + } + return $this->decryptNode($encKey, false); + } + + /** + * @param DOMDocument $element + * @return DOMNode|null + */ + public function locateEncryptedData($element) + { + if ($element instanceof DOMDocument) { + $doc = $element; + } else { + $doc = $element->ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $query = "//*[local-name()='EncryptedData' and namespace-uri()='".self::XMLENCNS."']"; + $nodeset = $xpath->query($query); + return $nodeset->item(0); + } + return null; + } + + /** + * Returns the key from the DOM + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $query = ".//xmlsecenc:EncryptionMethod"; + $nodeset = $xpath->query($query, $node); + if ($encmeth = $nodeset->item(0)) { + $attrAlgorithm = $encmeth->getAttribute("Algorithm"); + try { + $objKey = new XMLSecurityKey($attrAlgorithm, array('type' => 'private')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + * @throws Exception + */ + public static function staticLocateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node) || (! $node instanceof DOMNode)) { + return null; + } + $doc = $node->ownerDocument; + if (!$doc) { + return null; + } + + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('xmlsecenc', self::XMLENCNS); + $xpath->registerNamespace('xmlsecdsig', XMLSecurityDSig::XMLDSIGNS); + $query = "./xmlsecdsig:KeyInfo"; + $nodeset = $xpath->query($query, $node); + $encmeth = $nodeset->item(0); + if (!$encmeth) { + /* No KeyInfo in EncryptedData / EncryptedKey. */ + return $objBaseKey; + } + + foreach ($encmeth->childNodes AS $child) { + switch ($child->localName) { + case 'KeyName': + if (! empty($objBaseKey)) { + $objBaseKey->name = $child->nodeValue; + } + break; + case 'KeyValue': + foreach ($child->childNodes AS $keyval) { + switch ($keyval->localName) { + case 'DSAKeyValue': + throw new Exception("DSAKeyValue currently not supported"); + case 'RSAKeyValue': + $modulus = null; + $exponent = null; + if ($modulusNode = $keyval->getElementsByTagName('Modulus')->item(0)) { + $modulus = base64_decode($modulusNode->nodeValue); + } + if ($exponentNode = $keyval->getElementsByTagName('Exponent')->item(0)) { + $exponent = base64_decode($exponentNode->nodeValue); + } + if (empty($modulus) || empty($exponent)) { + throw new Exception("Missing Modulus or Exponent"); + } + $publicKey = XMLSecurityKey::convertRSA($modulus, $exponent); + $objBaseKey->loadKey($publicKey); + break; + } + } + break; + case 'RetrievalMethod': + $type = $child->getAttribute('Type'); + if ($type !== 'http://www.w3.org/2001/04/xmlenc#EncryptedKey') { + /* Unsupported key type. */ + break; + } + $uri = $child->getAttribute('URI'); + if ($uri[0] !== '#') { + /* URI not a reference - unsupported. */ + break; + } + $id = substr($uri, 1); + + $query = '//xmlsecenc:EncryptedKey[@Id="'.XPath::filterAttrValue($id, XPAth::DOUBLE_QUOTE).'"]'; + $keyElement = $xpath->query($query)->item(0); + if (!$keyElement) { + throw new Exception("Unable to locate EncryptedKey with @Id='$id'."); + } + + return XMLSecurityKey::fromEncryptedKeyElement($keyElement); + case 'EncryptedKey': + return XMLSecurityKey::fromEncryptedKeyElement($child); + case 'X509Data': + if ($x509certNodes = $child->getElementsByTagName('X509Certificate')) { + if ($x509certNodes->length > 0) { + $x509cert = $x509certNodes->item(0)->textContent; + $x509cert = str_replace(array("\r", "\n", " "), "", $x509cert); + $x509cert = "-----BEGIN CERTIFICATE-----\n".chunk_split($x509cert, 64, "\n")."-----END CERTIFICATE-----\n"; + $objBaseKey->loadKey($x509cert, false, true); + } + } + break; + } + } + return $objBaseKey; + } + + /** + * @param null|XMLSecurityKey $objBaseKey + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKeyInfo($objBaseKey=null, $node=null) + { + if (empty($node)) { + $node = $this->rawNode; + } + return self::staticLocateKeyInfo($objBaseKey, $node); + } +} diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php b/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php new file mode 100644 index 0000000..9eabde0 --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityDSig.php @@ -0,0 +1,1142 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityDSig +{ + const XMLDSIGNS = 'http://www.w3.org/2000/09/xmldsig#'; + const SHA1 = 'http://www.w3.org/2000/09/xmldsig#sha1'; + const SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'; + const SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#sha384'; + const SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'; + const RIPEMD160 = 'http://www.w3.org/2001/04/xmlenc#ripemd160'; + + const C14N = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + const C14N_COMMENTS = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments'; + const EXC_C14N = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + const EXC_C14N_COMMENTS = 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments'; + + const template = ' + + + +'; + + const BASE_TEMPLATE = ' + + + +'; + + /** @var DOMElement|null */ + public $sigNode = null; + + /** @var array */ + public $idKeys = array(); + + /** @var array */ + public $idNS = array(); + + /** @var string|null */ + private $signedInfo = null; + + /** @var DomXPath|null */ + private $xPathCtx = null; + + /** @var string|null */ + private $canonicalMethod = null; + + /** @var string */ + private $prefix = ''; + + /** @var string */ + private $searchpfx = 'secdsig'; + + /** + * This variable contains an associative array of validated nodes. + * @var array|null + */ + private $validatedNodes = null; + + /** + * @param string $prefix + */ + public function __construct($prefix='ds') + { + $template = self::BASE_TEMPLATE; + if (! empty($prefix)) { + $this->prefix = $prefix.':'; + $search = array("ownerDocument; + } + if ($doc) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = ".//secdsig:Signature"; + $nodeset = $xpath->query($query, $objDoc); + $this->sigNode = $nodeset->item($pos); + return $this->sigNode; + } + return null; + } + + /** + * @param string $name + * @param null|string $value + * @return DOMElement + */ + public function createNewSignNode($name, $value=null) + { + $doc = $this->sigNode->ownerDocument; + if (! is_null($value)) { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name, $value); + } else { + $node = $doc->createElementNS(self::XMLDSIGNS, $this->prefix.$name); + } + return $node; + } + + /** + * @param string $method + * @throws Exception + */ + public function setCanonicalMethod($method) + { + switch ($method) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $this->canonicalMethod = $method; + break; + default: + throw new Exception('Invalid Canonical Method'); + } + if ($xpath = $this->getXPathObj()) { + $query = './'.$this->searchpfx.':SignedInfo'; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sinfo = $nodeset->item(0)) { + $query = './'.$this->searchpfx.'CanonicalizationMethod'; + $nodeset = $xpath->query($query, $sinfo); + if (! ($canonNode = $nodeset->item(0))) { + $canonNode = $this->createNewSignNode('CanonicalizationMethod'); + $sinfo->insertBefore($canonNode, $sinfo->firstChild); + } + $canonNode->setAttribute('Algorithm', $this->canonicalMethod); + } + } + } + + /** + * @param DOMNode $node + * @param string $canonicalmethod + * @param null|array $arXPath + * @param null|array $prefixList + * @return string + */ + private function canonicalizeData($node, $canonicalmethod, $arXPath=null, $prefixList=null) + { + $exclusive = false; + $withComments = false; + switch ($canonicalmethod) { + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + $exclusive = false; + $withComments = false; + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + $withComments = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + $exclusive = true; + break; + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + $exclusive = true; + $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; + while ($refnode = $element->previousSibling) { + if ($refnode->nodeType == XML_PI_NODE || (($refnode->nodeType == XML_COMMENT_NODE) && $withComments)) { + break; + } + $element = $refnode; + } + if ($refnode == null) { + $node = $node->ownerDocument; + } + } + + return $node->C14N($exclusive, $withComments, $arXPath, $prefixList); + } + + /** + * @return null|string + */ + public function canonicalizeSignedInfo() + { + + $doc = $this->sigNode->ownerDocument; + $canonicalmethod = null; + if ($doc) { + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($signInfoNode = $nodeset->item(0)) { + $query = "./secdsig:CanonicalizationMethod"; + $nodeset = $xpath->query($query, $signInfoNode); + if ($canonNode = $nodeset->item(0)) { + $canonicalmethod = $canonNode->getAttribute('Algorithm'); + } + $this->signedInfo = $this->canonicalizeData($signInfoNode, $canonicalmethod); + return $this->signedInfo; + } + } + return null; + } + + /** + * @param string $digestAlgorithm + * @param string $data + * @param bool $encode + * @return string + * @throws Exception + */ + public function calculateDigest($digestAlgorithm, $data, $encode = true) + { + switch ($digestAlgorithm) { + case self::SHA1: + $alg = 'sha1'; + break; + case self::SHA256: + $alg = 'sha256'; + break; + case self::SHA384: + $alg = 'sha384'; + break; + case self::SHA512: + $alg = 'sha512'; + break; + case self::RIPEMD160: + $alg = 'ripemd160'; + break; + default: + throw new Exception("Cannot validate digest: Unsupported Algorithm <$digestAlgorithm>"); + } + + $digest = hash($alg, $data, true); + if ($encode) { + $digest = base64_encode($digest); + } + return $digest; + + } + + /** + * @param $refNode + * @param string $data + * @return bool + */ + public function validateDigest($refNode, $data) + { + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = 'string(./secdsig:DigestMethod/@Algorithm)'; + $digestAlgorithm = $xpath->evaluate($query, $refNode); + $digValue = $this->calculateDigest($digestAlgorithm, $data, false); + $query = 'string(./secdsig:DigestValue)'; + $digestValue = $xpath->evaluate($query, $refNode); + return ($digValue === base64_decode($digestValue)); + } + + /** + * @param $refNode + * @param DOMNode $objData + * @param bool $includeCommentNodes + * @return string + */ + public function processTransforms($refNode, $objData, $includeCommentNodes = true) + { + $data = $objData; + $xpath = new DOMXPath($refNode->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = './secdsig:Transforms/secdsig:Transform'; + $nodelist = $xpath->query($query, $refNode); + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + $arXPath = null; + $prefixList = null; + foreach ($nodelist AS $transform) { + $algorithm = $transform->getAttribute("Algorithm"); + switch ($algorithm) { + case 'http://www.w3.org/2001/10/xml-exc-c14n#': + case 'http://www.w3.org/2001/10/xml-exc-c14n#WithComments': + + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/2001/10/xml-exc-c14n#'; + } else { + $canonicalMethod = $algorithm; + } + + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'InclusiveNamespaces') { + if ($pfx = $node->getAttribute('PrefixList')) { + $arpfx = array(); + $pfxlist = explode(" ", $pfx); + foreach ($pfxlist AS $pfx) { + $val = trim($pfx); + if (! empty($val)) { + $arpfx[] = $val; + } + } + if (count($arpfx) > 0) { + $prefixList = $arpfx; + } + } + break; + } + $node = $node->nextSibling; + } + break; + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315': + case 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments': + if (!$includeCommentNodes) { + /* We remove comment nodes by forcing it to use a canonicalization + * without comments. + */ + $canonicalMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; + } else { + $canonicalMethod = $algorithm; + } + + break; + case 'http://www.w3.org/TR/1999/REC-xpath-19991116': + $node = $transform->firstChild; + while ($node) { + if ($node->localName == 'XPath') { + $arXPath = array(); + $arXPath['query'] = '(.//. | .//@* | .//namespace::*)['.$node->nodeValue.']'; + $arXpath['namespaces'] = array(); + $nslist = $xpath->query('./namespace::*', $node); + foreach ($nslist AS $nsnode) { + if ($nsnode->localName != "xml") { + $arXPath['namespaces'][$nsnode->localName] = $nsnode->nodeValue; + } + } + break; + } + $node = $node->nextSibling; + } + break; + } + } + if ($data instanceof DOMNode) { + $data = $this->canonicalizeData($objData, $canonicalMethod, $arXPath, $prefixList); + } + return $data; + } + + /** + * @param DOMNode $refNode + * @return bool + */ + public function processRefNode($refNode) + { + $dataObject = null; + + /* + * Depending on the URI, we may not want to include comments in the result + * See: http://www.w3.org/TR/xmldsig-core/#sec-ReferenceProcessingModel + */ + $includeCommentNodes = true; + + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + + /* This reference identifies a node with the given id by using + * a URI on the form "#identifier". This should not include comments. + */ + $includeCommentNodes = false; + + $xPath = new DOMXPath($refNode->ownerDocument); + if ($this->idNS && is_array($this->idNS)) { + foreach ($this->idNS AS $nspf => $ns) { + $xPath->registerNamespace($nspf, $ns); + } + } + $iDlist = '@Id="'.XPath::filterAttrValue($identifier, XPath::DOUBLE_QUOTE).'"'; + if (is_array($this->idKeys)) { + foreach ($this->idKeys AS $idKey) { + $iDlist .= " or @".XPath::filterAttrName($idKey).'="'. + XPATH::filterAttrValue($identifier, XPAth::DOUBLE_QUOTE).'"'; + } + } + $query = '//*['.$iDlist.']'; + $dataObject = $xPath->query($query)->item(0); + } else { + $dataObject = $refNode->ownerDocument; + } + } + } else { + /* This reference identifies the root node with an empty URI. This should + * not include comments. + */ + $includeCommentNodes = false; + + $dataObject = $refNode->ownerDocument; + } + $data = $this->processTransforms($refNode, $dataObject, $includeCommentNodes); + if (!$this->validateDigest($refNode, $data)) { + return false; + } + + if ($dataObject instanceof DOMNode) { + /* Add this node to the list of validated nodes. */ + if (! empty($identifier)) { + $this->validatedNodes[$identifier] = $dataObject; + } else { + $this->validatedNodes[] = $dataObject; + } + } + + return true; + } + + /** + * @param DOMNode $refNode + * @return null + */ + public function getRefNodeID($refNode) + { + if ($uri = $refNode->getAttribute("URI")) { + $arUrl = parse_url($uri); + if (empty($arUrl['path'])) { + if ($identifier = $arUrl['fragment']) { + return $identifier; + } + } + } + return null; + } + + /** + * @return array + * @throws Exception + */ + public function getRefIDs() + { + $refids = array(); + + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($nodeset->length == 0) { + throw new Exception("Reference nodes not found"); + } + foreach ($nodeset AS $refNode) { + $refids[] = $this->getRefNodeID($refNode); + } + return $refids; + } + + /** + * @return bool + * @throws Exception + */ + public function validateReference() + { + $docElem = $this->sigNode->ownerDocument->documentElement; + if (! $docElem->isSameNode($this->sigNode)) { + if ($this->sigNode->parentNode != null) { + $this->sigNode->parentNode->removeChild($this->sigNode); + } + } + $xpath = $this->getXPathObj(); + $query = "./secdsig:SignedInfo/secdsig:Reference"; + $nodeset = $xpath->query($query, $this->sigNode); + 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. */ + $this->validatedNodes = null; + throw new Exception("Reference validation failed"); + } + } + return true; + } + + /** + * @param DOMNode $sinfoNode + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + private function addRefInternal($sinfoNode, $node, $algorithm, $arTransforms=null, $options=null) + { + $prefix = null; + $prefix_ns = null; + $id_name = 'Id'; + $overwrite_id = true; + $force_uri = false; + + if (is_array($options)) { + $prefix = empty($options['prefix']) ? null : $options['prefix']; + $prefix_ns = empty($options['prefix_ns']) ? null : $options['prefix_ns']; + $id_name = empty($options['id_name']) ? 'Id' : $options['id_name']; + $overwrite_id = !isset($options['overwrite']) ? true : (bool) $options['overwrite']; + $force_uri = !isset($options['force_uri']) ? false : (bool) $options['force_uri']; + } + + $attname = $id_name; + if (! empty($prefix)) { + $attname = $prefix.':'.$attname; + } + + $refNode = $this->createNewSignNode('Reference'); + $sinfoNode->appendChild($refNode); + + if (! $node instanceof DOMDocument) { + $uri = null; + if (! $overwrite_id) { + $uri = $prefix_ns ? $node->getAttributeNS($prefix_ns, $id_name) : $node->getAttribute($id_name); + } + if (empty($uri)) { + $uri = self::generateGUID(); + $node->setAttributeNS($prefix_ns, $attname, $uri); + } + $refNode->setAttribute("URI", '#'.$uri); + } elseif ($force_uri) { + $refNode->setAttribute("URI", ''); + } + + $transNodes = $this->createNewSignNode('Transforms'); + $refNode->appendChild($transNodes); + + if (is_array($arTransforms)) { + 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'])) && + (! 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']); + $transNode->appendChild($XPathNode); + if (! empty($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'])) { + foreach ($transform['http://www.w3.org/TR/1999/REC-xpath-19991116']['namespaces'] AS $prefix => $namespace) { + $XPathNode->setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:$prefix", $namespace); + } + } + } else { + $transNode->setAttribute('Algorithm', $transform); + } + } + } elseif (! empty($this->canonicalMethod)) { + $transNode = $this->createNewSignNode('Transform'); + $transNodes->appendChild($transNode); + $transNode->setAttribute('Algorithm', $this->canonicalMethod); + } + + $canonicalData = $this->processTransforms($refNode, $node); + $digValue = $this->calculateDigest($algorithm, $canonicalData); + + $digestMethod = $this->createNewSignNode('DigestMethod'); + $refNode->appendChild($digestMethod); + $digestMethod->setAttribute('Algorithm', $algorithm); + + $digestValue = $this->createNewSignNode('DigestValue', $digValue); + $refNode->appendChild($digestValue); + } + + /** + * @param DOMDocument $node + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReference($node, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + + /** + * @param array $arNodes + * @param string $algorithm + * @param null|array $arTransforms + * @param null|array $options + */ + public function addReferenceList($arNodes, $algorithm, $arTransforms=null, $options=null) + { + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + foreach ($arNodes AS $node) { + $this->addRefInternal($sInfo, $node, $algorithm, $arTransforms, $options); + } + } + } + } + + /** + * @param DOMElement|string $data + * @param null|string $mimetype + * @param null|string $encoding + * @return DOMElement + */ + public function addObject($data, $mimetype=null, $encoding=null) + { + $objNode = $this->createNewSignNode('Object'); + $this->sigNode->appendChild($objNode); + if (! empty($mimetype)) { + $objNode->setAttribute('MimeType', $mimetype); + } + if (! empty($encoding)) { + $objNode->setAttribute('Encoding', $encoding); + } + + if ($data instanceof DOMElement) { + $newData = $this->sigNode->ownerDocument->importNode($data, true); + } else { + $newData = $this->sigNode->ownerDocument->createTextNode($data); + } + $objNode->appendChild($newData); + + return $objNode; + } + + /** + * @param null|DOMNode $node + * @return null|XMLSecurityKey + */ + public function locateKey($node=null) + { + if (empty($node)) { + $node = $this->sigNode; + } + if (! $node instanceof DOMNode) { + return null; + } + if ($doc = $node->ownerDocument) { + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignedInfo/secdsig:SignatureMethod/@Algorithm)"; + $algorithm = $xpath->evaluate($query, $node); + if ($algorithm) { + try { + $objKey = new XMLSecurityKey($algorithm, array('type' => 'public')); + } catch (Exception $e) { + return null; + } + return $objKey; + } + } + return null; + } + + /** + * Returns: + * Bool when verifying HMAC_SHA1; + * Int otherwise, with following meanings: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the int return value, because in + * PHP, -1 will be cast to True when in boolean context. Always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param XMLSecurityKey $objKey + * @return bool|int + * @throws Exception + */ + public function verify($objKey) + { + $doc = $this->sigNode->ownerDocument; + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + $query = "string(./secdsig:SignatureValue)"; + $sigValue = $xpath->evaluate($query, $this->sigNode); + if (empty($sigValue)) { + throw new Exception("Unable to locate SignatureValue"); + } + return $objKey->verifySignature($this->signedInfo, base64_decode($sigValue)); + } + + /** + * @param XMLSecurityKey $objKey + * @param string $data + * @return mixed|string + */ + public function signData($objKey, $data) + { + return $objKey->signData($data); + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $appendToNode + */ + public function sign($objKey, $appendToNode = null) + { + // If we have a parent node append it now so C14N properly works + if ($appendToNode != null) { + $this->resetXPathObj(); + $this->appendSignature($appendToNode); + $this->sigNode = $appendToNode->lastChild; + } + if ($xpath = $this->getXPathObj()) { + $query = "./secdsig:SignedInfo"; + $nodeset = $xpath->query($query, $this->sigNode); + if ($sInfo = $nodeset->item(0)) { + $query = "./secdsig:SignatureMethod"; + $nodeset = $xpath->query($query, $sInfo); + $sMethod = $nodeset->item(0); + $sMethod->setAttribute('Algorithm', $objKey->type); + $data = $this->canonicalizeData($sInfo, $this->canonicalMethod); + $sigValue = base64_encode($this->signData($objKey, $data)); + $sigValueNode = $this->createNewSignNode('SignatureValue', $sigValue); + if ($infoSibling = $sInfo->nextSibling) { + $infoSibling->parentNode->insertBefore($sigValueNode, $infoSibling); + } else { + $this->sigNode->appendChild($sigValueNode); + } + } + } + } + + public function appendCert() + { + + } + + /** + * @param XMLSecurityKey $objKey + * @param null|DOMNode $parent + */ + public function appendKey($objKey, $parent=null) + { + $objKey->serializeKey($parent); + } + + + /** + * This function inserts the signature element. + * + * The signature element will be appended to the element, unless $beforeNode is specified. If $beforeNode + * is specified, the signature element will be inserted as the last element before $beforeNode. + * + * @param DOMNode $node The node the signature element should be inserted into. + * @param DOMNode $beforeNode The node the signature element should be located before. + * + * @return DOMNode The signature element node + */ + public function insertSignature($node, $beforeNode = null) + { + + $document = $node->ownerDocument; + $signatureElement = $document->importNode($this->sigNode, true); + + if ($beforeNode == null) { + return $node->insertBefore($signatureElement); + } else { + return $node->insertBefore($signatureElement, $beforeNode); + } + } + + /** + * @param DOMNode $parentNode + * @param bool $insertBefore + * @return DOMNode + */ + public function appendSignature($parentNode, $insertBefore = false) + { + $beforeNode = $insertBefore ? $parentNode->firstChild : null; + return $this->insertSignature($parentNode, $beforeNode); + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @return string + */ + public static function get509XCert($cert, $isPEMFormat=true) + { + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + if (! empty($certs)) { + return $certs[0]; + } + return ''; + } + + /** + * @param string $certs + * @param bool $isPEMFormat + * @return array + */ + public static function staticGet509XCerts($certs, $isPEMFormat=true) + { + if ($isPEMFormat) { + $data = ''; + $certlist = array(); + $arCert = explode("\n", $certs); + $inData = false; + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + $inData = false; + $certlist[] = $data; + $data = ''; + continue; + } + $data .= trim($curData); + } + } + return $certlist; + } else { + return array($certs); + } + } + + /** + * @param DOMElement $parentRef + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|DOMXPath $xpath + * @param null|array $options + * @throws Exception + */ + public static function staticAdd509Cert($parentRef, $cert, $isPEMFormat=true, $isURL=false, $xpath=null, $options=null) + { + if ($isURL) { + $cert = file_get_contents($cert); + } + if (! $parentRef instanceof DOMElement) { + throw new Exception('Invalid parent Node parameter'); + } + $baseDoc = $parentRef->ownerDocument; + + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + $dsig_pfx = ''; + if (! $keyInfo) { + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } else { + $pfx = $keyInfo->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + } + + // Add all certs if there are more than one + $certs = self::staticGet509XCerts($cert, $isPEMFormat); + + // Attach X509 data node + $x509DataNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Data'); + $keyInfo->appendChild($x509DataNode); + + $issuerSerial = false; + $subjectName = false; + if (is_array($options)) { + if (! empty($options['issuerSerial'])) { + $issuerSerial = true; + } + if (! empty($options['subjectName'])) { + $subjectName = true; + } + } + + // Attach all certificate nodes and any additional data + foreach ($certs as $X509Cert) { + if ($issuerSerial || $subjectName) { + if ($certData = openssl_x509_parse("-----BEGIN CERTIFICATE-----\n".chunk_split($X509Cert, 64, "\n")."-----END CERTIFICATE-----\n")) { + if ($subjectName && ! empty($certData['subject'])) { + if (is_array($certData['subject'])) { + $parts = array(); + foreach ($certData['subject'] AS $key => $value) { + if (is_array($value)) { + foreach ($value as $valueElement) { + array_unshift($parts, "$key=$valueElement"); + } + } else { + array_unshift($parts, "$key=$value"); + } + } + $subjectNameValue = implode(',', $parts); + } else { + $subjectNameValue = $certData['issuer']; + } + $x509SubjectNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SubjectName', $subjectNameValue); + $x509DataNode->appendChild($x509SubjectNode); + } + if ($issuerSerial && ! empty($certData['issuer']) && ! empty($certData['serialNumber'])) { + if (is_array($certData['issuer'])) { + $parts = array(); + foreach ($certData['issuer'] AS $key => $value) { + array_unshift($parts, "$key=$value"); + } + $issuerName = implode(',', $parts); + } else { + $issuerName = $certData['issuer']; + } + + $x509IssuerNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerSerial'); + $x509DataNode->appendChild($x509IssuerNode); + + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509IssuerName', $issuerName); + $x509IssuerNode->appendChild($x509Node); + $x509Node = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509SerialNumber', $certData['serialNumber']); + $x509IssuerNode->appendChild($x509Node); + } + } + + } + $x509CertNode = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'X509Certificate', $X509Cert); + $x509DataNode->appendChild($x509CertNode); + } + } + + /** + * @param string $cert + * @param bool $isPEMFormat + * @param bool $isURL + * @param null|array $options + */ + public function add509Cert($cert, $isPEMFormat=true, $isURL=false, $options=null) + { + if ($xpath = $this->getXPathObj()) { + 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) + { + $parentRef = $this->sigNode; + $baseDoc = $parentRef->ownerDocument; + + $xpath = $this->getXPathObj(); + if (empty($xpath)) { + $xpath = new DOMXPath($parentRef->ownerDocument); + $xpath->registerNamespace('secdsig', self::XMLDSIGNS); + } + + $query = "./secdsig:KeyInfo"; + $nodeset = $xpath->query($query, $parentRef); + $keyInfo = $nodeset->item(0); + if (! $keyInfo) { + $dsig_pfx = ''; + $pfx = $parentRef->lookupPrefix(self::XMLDSIGNS); + if (! empty($pfx)) { + $dsig_pfx = $pfx.":"; + } + $inserted = false; + $keyInfo = $baseDoc->createElementNS(self::XMLDSIGNS, $dsig_pfx.'KeyInfo'); + + $query = "./secdsig:Object"; + $nodeset = $xpath->query($query, $parentRef); + if ($sObject = $nodeset->item(0)) { + $sObject->parentNode->insertBefore($keyInfo, $sObject); + $inserted = true; + } + + if (! $inserted) { + $parentRef->appendChild($keyInfo); + } + } + + $keyInfo->appendChild($node); + + return $keyInfo; + } + + /** + * This function retrieves an associative array of the validated nodes. + * + * The array will contain the id of the referenced node as the key and the node itself + * as the value. + * + * Returns: + * An associative array of validated nodes or null if no nodes have been validated. + * + * @return array Associative array of validated nodes + */ + public function getValidatedNodes() + { + return $this->validatedNodes; + } +} diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php b/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php new file mode 100644 index 0000000..5d855bf --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php @@ -0,0 +1,749 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + */ + +class XMLSecurityKey +{ + const TRIPLEDES_CBC = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + const AES128_CBC = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + const AES192_CBC = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + const AES256_CBC = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + const RSA_1_5 = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + const RSA_OAEP_MGF1P = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + const DSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'; + const RSA_SHA1 = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + const RSA_SHA256 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + const RSA_SHA384 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + const RSA_SHA512 = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + const HMAC_SHA1 = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + + /** @var array */ + private $cryptParams = array(); + + /** @var int|string */ + public $type = 0; + + /** @var mixed|null */ + public $key = null; + + /** @var string */ + public $passphrase = ""; + + /** @var string|null */ + public $iv = null; + + /** @var string|null */ + public $name = null; + + /** @var mixed|null */ + public $keyChain = null; + + /** @var bool */ + public $isEncrypted = false; + + /** @var XMLSecEnc|null */ + public $encryptedCtx = null; + + /** @var mixed|null */ + public $guid = null; + + /** + * This variable contains the certificate as a string if this key represents an X509-certificate. + * If this key doesn't represent a certificate, this will be null. + * @var string|null + */ + private $x509Certificate = null; + + /** + * This variable contains the certificate thumbprint if we have loaded an X509-certificate. + * @var string|null + */ + private $X509Thumbprint = null; + + /** + * @param string $type + * @param null|array $params + * @throws Exception + */ + public function __construct($type, $params=null) + { + switch ($type) { + case (self::TRIPLEDES_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'des-ede3-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 8; + break; + case (self::AES128_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-128-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes128-cbc'; + $this->cryptParams['keysize'] = 16; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES192_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-192-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes192-cbc'; + $this->cryptParams['keysize'] = 24; + $this->cryptParams['blocksize'] = 16; + break; + case (self::AES256_CBC): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['cipher'] = 'aes-256-cbc'; + $this->cryptParams['type'] = 'symmetric'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#aes256-cbc'; + $this->cryptParams['keysize'] = 32; + $this->cryptParams['blocksize'] = 16; + break; + case (self::RSA_1_5): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-1_5'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_OAEP_MGF1P): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_OAEP_PADDING; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p'; + $this->cryptParams['hash'] = null; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA1): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA256): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA256'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA384): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA384'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::RSA_SHA512): + $this->cryptParams['library'] = 'openssl'; + $this->cryptParams['method'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'; + $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING; + $this->cryptParams['digest'] = 'SHA512'; + if (is_array($params) && ! empty($params['type'])) { + if ($params['type'] == 'public' || $params['type'] == 'private') { + $this->cryptParams['type'] = $params['type']; + break; + } + } + throw new Exception('Certificate "type" (private/public) must be passed via parameters'); + case (self::HMAC_SHA1): + $this->cryptParams['library'] = $type; + $this->cryptParams['method'] = 'http://www.w3.org/2000/09/xmldsig#hmac-sha1'; + break; + default: + throw new Exception('Invalid Key Type'); + } + $this->type = $type; + } + + /** + * Retrieve the key size for the symmetric encryption algorithm.. + * + * If the key size is unknown, or this isn't a symmetric encryption algorithm, + * null is returned. + * + * @return int|null The number of bytes in the key. + */ + public function getSymmetricKeySize() + { + if (! isset($this->cryptParams['keysize'])) { + return null; + } + return $this->cryptParams['keysize']; + } + + /** + * Generates a session key using the openssl-extension. + * In case of using DES3-CBC the key is checked for a proper parity bits set. + * @return string + * @throws Exception + */ + public function generateSessionKey() + { + if (!isset($this->cryptParams['keysize'])) { + throw new Exception('Unknown key size for type "' . $this->type . '".'); + } + $keysize = $this->cryptParams['keysize']; + + $key = openssl_random_pseudo_bytes($keysize); + + if ($this->type === self::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. + */ + for ($i = 0; $i < strlen($key); $i++) { + $byte = ord($key[$i]) & 0xfe; + $parity = 1; + for ($j = 1; $j < 8; $j++) { + $parity ^= ($byte >> $j) & 1; + } + $byte |= $parity; + $key[$i] = chr($byte); + } + } + + $this->key = $key; + return $key; + } + + /** + * Get the raw thumbprint of a certificate + * + * @param string $cert + * @return null|string + */ + public static function getRawThumbprint($cert) + { + + $arCert = explode("\n", $cert); + $data = ''; + $inData = false; + + foreach ($arCert AS $curData) { + if (! $inData) { + if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) == 0) { + $inData = true; + } + } else { + if (strncmp($curData, '-----END CERTIFICATE', 20) == 0) { + break; + } + $data .= trim($curData); + } + } + + if (! empty($data)) { + return strtolower(sha1(base64_decode($data))); + } + + return null; + } + + /** + * Loads the given key, or - with isFile set true - the key from the keyfile. + * + * @param string $key + * @param bool $isFile + * @param bool $isCert + * @throws Exception + */ + public function loadKey($key, $isFile=false, $isCert = false) + { + if ($isFile) { + $this->key = file_get_contents($key); + } else { + $this->key = $key; + } + if ($isCert) { + $this->key = openssl_x509_read($this->key); + openssl_x509_export($this->key, $str_cert); + $this->x509Certificate = $str_cert; + $this->key = $str_cert; + } else { + $this->x509Certificate = null; + } + if ($this->cryptParams['library'] == 'openssl') { + switch ($this->cryptParams['type']) { + case 'public': + if ($isCert) { + /* Load the thumbprint if this is an X509 certificate. */ + $this->X509Thumbprint = self::getRawThumbprint($this->key); + } + $this->key = openssl_get_publickey($this->key); + if (! $this->key) { + throw new Exception('Unable to extract public key'); + } + break; + + case 'private': + $this->key = openssl_get_privatekey($this->key, $this->passphrase); + break; + + case'symmetric': + if (strlen($this->key) < $this->cryptParams['keysize']) { + throw new Exception('Key must contain at least 25 characters for this cipher'); + } + break; + + default: + throw new Exception('Unknown type'); + } + } + } + + /** + * ISO 10126 Padding + * + * @param string $data + * @param integer $blockSize + * @throws Exception + * @return string + */ + private function padISO10126($data, $blockSize) + { + if ($blockSize > 256) { + throw new Exception('Block size higher than 256 not allowed'); + } + $padChr = $blockSize - (strlen($data) % $blockSize); + $pattern = chr($padChr); + return $data . str_repeat($pattern, $padChr); + } + + /** + * Remove ISO 10126 Padding + * + * @param string $data + * @return string + */ + private function unpadISO10126($data) + { + $padChr = substr($data, -1); + $padLen = ord($padChr); + return substr($data, 0, -$padLen); + } + + /** + * Encrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function encryptSymmetric($data) + { + $this->iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->cryptParams['cipher'])); + $data = $this->padISO10126($data, $this->cryptParams['blocksize']); + $encrypted = openssl_encrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + if (false === $encrypted) { + throw new Exception('Failure encrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return $this->iv . $encrypted; + } + + /** + * Decrypts the given data (string) using the openssl-extension + * + * @param string $data + * @return string + */ + private function decryptSymmetric($data) + { + $iv_length = openssl_cipher_iv_length($this->cryptParams['cipher']); + $this->iv = substr($data, 0, $iv_length); + $data = substr($data, $iv_length); + $decrypted = openssl_decrypt($data, $this->cryptParams['cipher'], $this->key, OPENSSL_RAW_DATA | OPENSSL_ZERO_PADDING, $this->iv); + if (false === $decrypted) { + throw new Exception('Failure decrypting Data (openssl symmetric) - ' . openssl_error_string()); + } + return $this->unpadISO10126($decrypted); + } + + /** + * Encrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPublic($data) + { + if (! openssl_public_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl public) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given public data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPublic($data) + { + if (! openssl_public_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl public) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Encrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function encryptPrivate($data) + { + if (! openssl_private_encrypt($data, $encrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure encrypting Data (openssl private) - ' . openssl_error_string()); + } + return $encrypted; + } + + /** + * Decrypts the given private data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function decryptPrivate($data) + { + if (! openssl_private_decrypt($data, $decrypted, $this->key, $this->cryptParams['padding'])) { + throw new Exception('Failure decrypting Data (openssl private) - ' . openssl_error_string()); + } + return $decrypted; + } + + /** + * Signs the given data (string) using the openssl-extension + * + * @param string $data + * @return string + * @throws Exception + */ + private function signOpenSSL($data) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + if (! openssl_sign($data, $signature, $this->key, $algo)) { + throw new Exception('Failure Signing Data: ' . openssl_error_string() . ' - ' . $algo); + } + return $signature; + } + + /** + * Verifies the given data (string) belonging to the given signature using the openssl-extension + * + * Returns: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return int + */ + private function verifyOpenSSL($data, $signature) + { + $algo = OPENSSL_ALGO_SHA1; + if (! empty($this->cryptParams['digest'])) { + $algo = $this->cryptParams['digest']; + } + return openssl_verify($data, $signature, $this->key, $algo); + } + + /** + * Encrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function encryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->encryptSymmetric($data); + case 'public': + return $this->encryptPublic($data); + case 'private': + return $this->encryptPrivate($data); + } + } + } + + /** + * Decrypts the given data (string) using the regarding php-extension, depending on the library assigned to algorithm in the contructor. + * + * @param string $data + * @return mixed|string + */ + public function decryptData($data) + { + if ($this->cryptParams['library'] === 'openssl') { + switch ($this->cryptParams['type']) { + case 'symmetric': + return $this->decryptSymmetric($data); + case 'public': + return $this->decryptPublic($data); + case 'private': + return $this->decryptPrivate($data); + } + } + } + + /** + * Signs the data (string) using the extension assigned to the type in the constructor. + * + * @param string $data + * @return mixed|string + */ + public function signData($data) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->signOpenSSL($data); + case (self::HMAC_SHA1): + return hash_hmac("sha1", $data, $this->key, true); + } + } + + /** + * Verifies the data (string) against the given signature using the extension assigned to the type in the constructor. + * + * Returns in case of openSSL: + * 1 on succesful signature verification, + * 0 when signature verification failed, + * -1 if an error occurred during processing. + * + * NOTE: be very careful when checking the return value, because in PHP, + * -1 will be cast to True when in boolean context. So always check the + * return value in a strictly typed way, e.g. "$obj->verify(...) === 1". + * + * @param string $data + * @param string $signature + * @return bool|int + */ + public function verifySignature($data, $signature) + { + switch ($this->cryptParams['library']) { + case 'openssl': + return $this->verifyOpenSSL($data, $signature); + case (self::HMAC_SHA1): + $expectedSignature = hash_hmac("sha1", $data, $this->key, true); + return strcmp($signature, $expectedSignature) == 0; + } + } + + /** + * @deprecated + * @see getAlgorithm() + * @return mixed + */ + public function getAlgorith() + { + return $this->getAlgorithm(); + } + + /** + * @return mixed + */ + public function getAlgorithm() + { + return $this->cryptParams['method']; + } + + /** + * + * @param int $type + * @param string $string + * @return null|string + */ + public static function makeAsnSegment($type, $string) + { + switch ($type) { + case 0x02: + if (ord($string) > 0x7f) + $string = chr(0).$string; + break; + case 0x03: + $string = chr(0).$string; + break; + } + + $length = strlen($string); + + if ($length < 128) { + $output = sprintf("%c%c%s", $type, $length, $string); + } else if ($length < 0x0100) { + $output = sprintf("%c%c%c%s", $type, 0x81, $length, $string); + } else if ($length < 0x010000) { + $output = sprintf("%c%c%c%c%s", $type, 0x82, $length / 0x0100, $length % 0x0100, $string); + } else { + $output = null; + } + return $output; + } + + /** + * + * Hint: Modulus and Exponent must already be base64 decoded + * @param string $modulus + * @param string $exponent + * @return string + */ + public static function convertRSA($modulus, $exponent) + { + /* make an ASN publicKeyInfo */ + $exponentEncoding = self::makeAsnSegment(0x02, $exponent); + $modulusEncoding = self::makeAsnSegment(0x02, $modulus); + $sequenceEncoding = self::makeAsnSegment(0x30, $modulusEncoding.$exponentEncoding); + $bitstringEncoding = self::makeAsnSegment(0x03, $sequenceEncoding); + $rsaAlgorithmIdentifier = pack("H*", "300D06092A864886F70D0101010500"); + $publicKeyInfo = self::makeAsnSegment(0x30, $rsaAlgorithmIdentifier.$bitstringEncoding); + + /* encode the publicKeyInfo in base64 and add PEM brackets */ + $publicKeyInfoBase64 = base64_encode($publicKeyInfo); + $encoding = "-----BEGIN PUBLIC KEY-----\n"; + $offset = 0; + while ($segment = substr($publicKeyInfoBase64, $offset, 64)) { + $encoding = $encoding.$segment."\n"; + $offset += 64; + } + return $encoding."-----END PUBLIC KEY-----\n"; + } + + /** + * @param mixed $parent + */ + public function serializeKey($parent) + { + + } + + /** + * Retrieve the X509 certificate this key represents. + * + * Will return the X509 certificate in PEM-format if this key represents + * an X509 certificate. + * + * @return string The X509 certificate or null if this key doesn't represent an X509-certificate. + */ + public function getX509Certificate() + { + return $this->x509Certificate; + } + + /** + * Get the thumbprint of this X509 certificate. + * + * Returns: + * The thumbprint as a lowercase 40-character hexadecimal number, or null + * if this isn't a X509 certificate. + * + * @return string Lowercase 40-character hexadecimal number of thumbprint + */ + public function getX509Thumbprint() + { + return $this->X509Thumbprint; + } + + + /** + * Create key from an EncryptedKey-element. + * + * @param DOMElement $element The EncryptedKey-element. + * @throws Exception + * + * @return XMLSecurityKey The new key. + */ + public static function fromEncryptedKeyElement(DOMElement $element) + { + + $objenc = new XMLSecEnc(); + $objenc->setNode($element); + if (! $objKey = $objenc->locateKey()) { + throw new Exception("Unable to locate algorithm for this Encrypted Key"); + } + $objKey->isEncrypted = true; + $objKey->encryptedCtx = $objenc; + XMLSecEnc::staticLocateKeyInfo($objKey, $element); + return $objKey; + } + +} diff --git a/spid-php-lib/vendor/robrichards/xmlseclibs/xmlseclibs.php b/spid-php-lib/vendor/robrichards/xmlseclibs/xmlseclibs.php new file mode 100644 index 0000000..b5322b8 --- /dev/null +++ b/spid-php-lib/vendor/robrichards/xmlseclibs/xmlseclibs.php @@ -0,0 +1,47 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Robert Richards nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @author Robert Richards + * @copyright 2007-2018 Robert Richards + * @license http://www.opensource.org/licenses/bsd-license.php BSD License + * @version 3.0.2 + */ + +$xmlseclibs_srcdir = dirname(__FILE__) . '/src/'; +require $xmlseclibs_srcdir . '/XMLSecurityKey.php'; +require $xmlseclibs_srcdir . '/XMLSecurityDSig.php'; +require $xmlseclibs_srcdir . '/XMLSecEnc.php'; +require $xmlseclibs_srcdir . '/Utils/XPath.php';