From 46ce6e234d964e2204a3f943c5359a929d7009b4 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 20 Feb 2023 09:09:30 +0800 Subject: [PATCH 01/12] set default password if the password is not set --- src/letswifi/profile/generator/pkcs12generator.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/letswifi/profile/generator/pkcs12generator.php b/src/letswifi/profile/generator/pkcs12generator.php index deebe6a..5345f86 100644 --- a/src/letswifi/profile/generator/pkcs12generator.php +++ b/src/letswifi/profile/generator/pkcs12generator.php @@ -26,7 +26,11 @@ class PKCS12Generator extends AbstractGenerator public function __construct( IProfileData $profileData, array $authenticationMethods, string $password = '' ) { parent::__construct( $profileData, $authenticationMethods ); - $this->password = $password; + if ( empty($password) ) { + $this->password = 'pkcs12'; + } else { + $this->password = $password; + } } /** From 819d6c6c8f96752463f4e5fefdf214480b1e9fe5 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 20 Feb 2023 09:11:15 +0800 Subject: [PATCH 02/12] added OR settlement free RCOI --- src/letswifi/profile/eduroamprofiledata.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/letswifi/profile/eduroamprofiledata.php b/src/letswifi/profile/eduroamprofiledata.php index 21a585e..05d8773 100644 --- a/src/letswifi/profile/eduroamprofiledata.php +++ b/src/letswifi/profile/eduroamprofiledata.php @@ -28,6 +28,8 @@ public function getNetworks(): array { $result = parent::getNetworks(); $result[] = new HS20Network( '001bc50460' ); + $result[] = new HS20Network( '5a03ba0000' ); + $result[] = new HS20Network( '5a03ba0800' ); $result[] = new SSIDNetwork( 'eduroam' ); return $result; From 747ef0c99f0ff892269d8e36520415b6e9bfe208 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:42:45 +0800 Subject: [PATCH 03/12] Updated OR and eduroam branding --- src/letswifi/profile/generator/mobileconfiggenerator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/letswifi/profile/generator/mobileconfiggenerator.php b/src/letswifi/profile/generator/mobileconfiggenerator.php index 78d643f..c85423f 100644 --- a/src/letswifi/profile/generator/mobileconfiggenerator.php +++ b/src/letswifi/profile/generator/mobileconfiggenerator.php @@ -183,9 +183,9 @@ static function ( $_ ){ return static::uuidgen(); }, * but the code is clearer this way. */ if ( $network instanceof SSIDNetwork ) { - $payloadDisplayName = static::e( $network->getSSID() ); + $payloadDisplayName = static::e( $network->getSSID().'®' ); } elseif ( $network instanceof HS20Network ) { - $payloadDisplayName = 'roaming via Passpoint'; + $payloadDisplayName = 'roaming via OpenRoaming®'; } else { throw new InvalidArgumentException( 'Only SSID or Hotspot 2.0 networks are supported, got ' . \get_class( $network ) ); } @@ -222,7 +222,7 @@ static function ( $_ ){ return static::uuidgen(); }, . "\n" . ' ServiceProviderRoamingEnabled' . "\n" . ' ' . "\n" . ' DisplayedOperatorName' - . "\n" . ' ' . static::e( $this->profileData->getRealm() ) . ' via Passpoint' + . "\n" . ' ' . static::e( $this->profileData->getRealm() ) . ' via OpenRoaming®' . "\n" . ' DomainName' . "\n" . ' ' . static::e( $this->profileData->getRealm() ) . '' . "\n" . ' RoamingConsortiumOIs' From a320e2aacf022aa3fa42e5d07afb4290b5fc1a95 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 20 Feb 2023 11:55:23 +0800 Subject: [PATCH 04/12] Added geteduroam link for huawei phone --- www/app/index.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/app/index.php b/www/app/index.php index 376682e..2d6bb9b 100644 --- a/www/app/index.php +++ b/www/app/index.php @@ -29,6 +29,10 @@ 'url' => 'https://geteduroam.app/app/geteduroam.exe', 'name' => 'Windows', ], + 'huawei' => [ + 'url' => 'https://appgallery.huawei.com/app/C104231893', + 'name' => 'Huawei', + ], ], 'mobileconfig' => [ 'url' => "${basePath}/profiles/mac/", From d9534f0560b6b40c0e0bfe2eec1277af2f8beefc Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Sat, 4 Mar 2023 11:48:26 +0800 Subject: [PATCH 05/12] Added sample configuration from sifulan --- etc/letswifi.conf.sifulan.php | 38 +++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 etc/letswifi.conf.sifulan.php diff --git a/etc/letswifi.conf.sifulan.php b/etc/letswifi.conf.sifulan.php new file mode 100644 index 0000000..ec42b60 --- /dev/null +++ b/etc/letswifi.conf.sifulan.php @@ -0,0 +1,38 @@ + 'SimpleSAMLAuth', + 'auth.params' => [ + 'autoloadInclude' => dirname( __DIR__ ) . '/simplesamlphp/lib/_autoload.php', + 'authSource' => 'default-sp', + ], + 'realm.selector' => 'httphost', // one of null, getparam or httphost + 'realm.default' => 'get.eduroam.my', // used when realm.selector = null + 'realm.auth' => [ + 'get.eduroam.my' => [ + 'userIdAttribute' => 'mail', + #'authzAttributeValue' => [ + # 'urn:oid:1.3.6.1.4.1.5923.1.1.1.1' => ['member','staff','student'] + #], + ], + ], + 'pdo.dsn' => 'mysql:host=;dbname=', + 'pdo.username' => '', + 'pdo.password' => '', + # You can use this command to convert your multiline PEM file to a single line: awk 'NF {sub(/\r/, ""); printf "%s\\n",$0;}' < your.pem > + 'profile.signing.cert' => "", + 'profile.signing.key' => "", + 'profile.signing.ca' => [], + 'profile.signing.passphrase' => '', + 'auth.admin-ca-index' => [ '' ], + 'auth.admin-ca-revoke' => [ '' ], + 'oauth.clients' => (require __DIR__ . DIRECTORY_SEPARATOR . 'clients.php') + /* + 'oauth.clients' => (require __DIR__ . DIRECTORY_SEPARATOR . 'clients.php') + [ + [ + 'clientId' => 'app.geteduroam.sh', + 'redirectUris' => ['http://[::1]/callback/'], + 'scopes' => ['eap-metadata', 'testscope'], + 'refresh' => false, + ], + ], + */ +]; From c91d6491adc50920d351984d53119ebf715b84d2 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Sat, 4 Mar 2023 11:49:49 +0800 Subject: [PATCH 06/12] Patched code to include intermediate CA certs --- src/letswifi/letswifiapp.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/letswifi/letswifiapp.php b/src/letswifi/letswifiapp.php index 837806d..9a1a875 100644 --- a/src/letswifi/letswifiapp.php +++ b/src/letswifi/letswifiapp.php @@ -205,10 +205,19 @@ public function getProfileSigner(): ?PKCS7 $passphrase = $this->config->getStringOrNull( 'profile.signing.passphrase' ); + $signingCA = $this->config->getArrayOrEmpty( 'profile.signing.ca' ); + if ( null !== $signingCA ) { + $signingCAChain = []; + foreach ( $signingCA as $ca) { + $signingCAChain[] = new X509($ca); + } + $signingCA = $signingCAChain; + } + $certificate = new X509( $signingCert ); $key = new PrivateKey( $signingKey, $passphrase ); - return new PKCS7( $certificate, $key ); + return new PKCS7( $certificate, $key , $signingCA); } /** From a93095b03b63c5b7a731e34a012dd0e5786d7ee5 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Sat, 4 Mar 2023 11:49:59 +0800 Subject: [PATCH 07/12] added .DS_Store --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5946f05..f465084 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ /.php-cs-fixer.cache /.phpunit.result.cache *.eap-config +.DS_Store From 7dbcc653324f29cda78ea010507d3be35b9a4232 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Tue, 7 Mar 2023 23:13:13 +0800 Subject: [PATCH 08/12] Added (r) symbol --- src/letswifi/profile/eduroamprofiledata.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/letswifi/profile/eduroamprofiledata.php b/src/letswifi/profile/eduroamprofiledata.php index 05d8773..d9962f1 100644 --- a/src/letswifi/profile/eduroamprofiledata.php +++ b/src/letswifi/profile/eduroamprofiledata.php @@ -16,7 +16,7 @@ class EduroamProfileData extends AbstractProfileData { - public function __construct( string $realm, string $displayName = 'eduroam', array $data = [], string $languageCode = 'en' ) + public function __construct( string $realm, string $displayName = 'eduroam®', array $data = [], string $languageCode = 'en' ) { parent::__construct( $realm, $displayName, $data, $languageCode ); } From ee5a0015d0ec620d721016b5b36f9e654a47892c Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Thu, 9 Mar 2023 13:56:22 +0800 Subject: [PATCH 09/12] Set the RCOI to uppercase due to ios issue --- src/letswifi/profile/eduroamprofiledata.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/letswifi/profile/eduroamprofiledata.php b/src/letswifi/profile/eduroamprofiledata.php index d9962f1..0c4e99a 100644 --- a/src/letswifi/profile/eduroamprofiledata.php +++ b/src/letswifi/profile/eduroamprofiledata.php @@ -27,9 +27,9 @@ public function __construct( string $realm, string $displayName = 'eduroam®', a public function getNetworks(): array { $result = parent::getNetworks(); - $result[] = new HS20Network( '001bc50460' ); - $result[] = new HS20Network( '5a03ba0000' ); - $result[] = new HS20Network( '5a03ba0800' ); + $result[] = new HS20Network( '001BC50460' ); + $result[] = new HS20Network( '5A03BA0000' ); + $result[] = new HS20Network( '5A03BA0800' ); $result[] = new SSIDNetwork( 'eduroam' ); return $result; From d4963ac21ef2f028a6cdb1e2d06770736d13b329 Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:20:50 +0800 Subject: [PATCH 10/12] Revert "Disable default encryption on ONC profiles" --- www/profiles/onc/index.php | 1 + 1 file changed, 1 insertion(+) diff --git a/www/profiles/onc/index.php b/www/profiles/onc/index.php index 2d1e712..006fa2d 100644 --- a/www/profiles/onc/index.php +++ b/www/profiles/onc/index.php @@ -14,5 +14,6 @@ $downloadKind = 'google-onc'; $href = "${basePath}/profiles/onc/"; +$passphrase = $_COOKIE["${downloadKind}-download-passphrase"] ?: \substr( '000' . \random_int( 0, 9999 ), -4 ); require \implode(\DIRECTORY_SEPARATOR, [\dirname(__DIR__), 'new', '_download.php']); From 42ae99087b32018eddedab9e77e2f5e20315d52e Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:21:26 +0800 Subject: [PATCH 11/12] Revert "Merged with the latest update from upstream" --- bin/onc-decrypt.php | 12 +----------- .../profile/generator/oncgenerator.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bin/onc-decrypt.php b/bin/onc-decrypt.php index 0066794..dea10f0 100755 --- a/bin/onc-decrypt.php +++ b/bin/onc-decrypt.php @@ -11,18 +11,8 @@ $initVector = \base64_decode( $parsed['IV'], true ); $password = $argv[1]; -$encryptionKey = \hash_pbkdf2( 'sha1', $password, $salt, $parsed['Iterations'], 32, true ); +$encryptionKey = \hash_pbkdf2( 'sha1', $password, $salt, $parsed['Iterations'], 32, true); $data = \openssl_decrypt( \base64_decode( $parsed['Ciphertext'], true ), 'AES-256-CBC', $encryptionKey, \OPENSSL_RAW_DATA, $initVector ); -$hmac = \hash_hmac( 'sha1', \base64_decode( $parsed['Ciphertext'], true ), $encryptionKey, true ); - -if ( 'SHA1' !== $parsed['HMACMethod'] ) { - echo "Invalid HMAC algo\n"; - exit(2); -} -if ( \base64_decode( $parsed['HMAC'], true ) !== $hmac ) { - echo "Invalid HMAC\n"; - exit(2); -} if ( $data ) { echo "${data}\n"; diff --git a/src/letswifi/profile/generator/oncgenerator.php b/src/letswifi/profile/generator/oncgenerator.php index 7004727..f20b12f 100644 --- a/src/letswifi/profile/generator/oncgenerator.php +++ b/src/letswifi/profile/generator/oncgenerator.php @@ -61,23 +61,23 @@ protected static function encrypt( string $clearText, string $passphrase ): arra // TODO see if hmac can use something stronger than SHA1 $salt = \random_bytes( 12 ); - $key = \hash_pbkdf2( 'sha1', $passphrase, $salt, self::PBKDF2_ITERATIONS, 32, true ); - $iv = \random_bytes( 16); - $cipherText = \openssl_encrypt( $clearText, 'AES-256-CBC', $key, \OPENSSL_RAW_DATA, $iv ); + $key = \hash_pbkdf2('sha1', $passphrase, $salt, self::PBKDF2_ITERATIONS, 32, true); + $iv = \random_bytes(16); + $cipherText = \openssl_encrypt($clearText, 'AES-256-CBC', $key, \OPENSSL_RAW_DATA, $iv); if ( false === $cipherText ) { throw new RuntimeException( 'Unable to encrypt profile' ); } - $hmac = \hash_hmac( 'sha1', $cipherText, $key, true ); + $hmac = \hash_hmac('sha1', $cipherText, $key, true); return [ 'Cipher' => 'AES256', 'Ciphertext' => \base64_encode( $cipherText ), - 'HMAC' => \base64_encode( $hmac ), + 'HMAC' => \base64_encode($hmac), 'HMACMethod' => 'SHA1', - 'Salt' => \base64_encode( $salt ), + 'Salt' => \base64_encode($salt), 'Stretch' => 'PBKDF2', 'Iterations' => self::PBKDF2_ITERATIONS, - 'IV' => \base64_encode( $iv ), + 'IV' => \base64_encode($iv), 'Type' => 'EncryptedConfiguration', ]; } @@ -120,7 +120,7 @@ static function ($a): bool { return $a instanceof TlsAuth && null !== $a->getPKC return [ 'Type' => 'UnencryptedConfiguration', 'Certificates' => \array_merge( $caCertificates, [$clientCertificate] ), - 'NetworkConfigurations' => \array_values( $networkConfigurations ), + 'NetworkConfigurations' => $networkConfigurations, ]; } @@ -153,7 +153,7 @@ protected static function generateNetworkConfiguration( Network $network, string 'Identity' => $clientCertCN, 'Outer' => 'EAP-TLS', 'SaveCredentials' => true, - 'ServerCARefs' => \array_values( $caIDs ), + 'ServerCARefs' => $caIDs, 'SubjectMatch' => $serverSubjectMatch, 'UseSystemCAs' => false, ], From 309bf625fdb62a9fa28b9c1dc94a00553a71c90a Mon Sep 17 00:00:00 2001 From: Muhammad Farhan Sjaugi <7088099+farhansj@users.noreply.github.com> Date: Mon, 6 Nov 2023 21:24:13 +0800 Subject: [PATCH 12/12] Revert "Update ifirexman's letswifi-portal" --- bin/onc-decrypt.php | 19 - composer.json | 4 +- composer.lock | 619 +++++++++--------- src/letswifi/browserauth/simplesamlauth.php | 4 - src/letswifi/letswifiapp.php | 25 +- src/letswifi/profile/auth/tlsauth.php | 12 +- .../profile/generator/abstractgenerator.php | 9 +- .../profile/generator/eapconfiggenerator.php | 93 +-- .../generator/mobileconfiggenerator.php | 21 +- .../profile/generator/oncgenerator.php | 238 ------- .../profile/generator/pkcs12generator.php | 17 +- src/letswifi/realm/realm.php | 19 +- tpl/app.html | 11 +- tpl/mobileconfig-mac-new.html | 21 + tpl/profile-advanced.html | 37 -- tpl/profile-download.html | 39 -- tpl/profiles-new.html | 23 + www/app/index.php | 11 +- www/assets/geteduroam.css | 13 +- www/index.php | 2 +- www/profiles/mac/index.php | 33 +- www/profiles/new/_download.php | 57 -- www/profiles/new/index.php | 57 +- www/profiles/onc/index.php | 19 - 24 files changed, 528 insertions(+), 875 deletions(-) delete mode 100755 bin/onc-decrypt.php delete mode 100644 src/letswifi/profile/generator/oncgenerator.php create mode 100644 tpl/mobileconfig-mac-new.html delete mode 100644 tpl/profile-advanced.html delete mode 100644 tpl/profile-download.html create mode 100644 tpl/profiles-new.html delete mode 100644 www/profiles/new/_download.php delete mode 100644 www/profiles/onc/index.php diff --git a/bin/onc-decrypt.php b/bin/onc-decrypt.php deleted file mode 100755 index dea10f0..0000000 --- a/bin/onc-decrypt.php +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env php -output.onc\n", $argv[0] ); - exit(1); -} - -$input = \file_get_contents( 'php://stdin' ); -$parsed = \json_decode( $input, true ); -$salt = \base64_decode( $parsed['Salt'], true ); -$initVector = \base64_decode( $parsed['IV'], true ); - -$password = $argv[1]; -$encryptionKey = \hash_pbkdf2( 'sha1', $password, $salt, $parsed['Iterations'], 32, true); -$data = \openssl_decrypt( \base64_decode( $parsed['Ciphertext'], true ), 'AES-256-CBC', $encryptionKey, \OPENSSL_RAW_DATA, $initVector ); - -if ( $data ) { - echo "${data}\n"; -} diff --git a/composer.json b/composer.json index c38a638..cb90d7d 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,8 @@ "require": { "php": ">=7.3", "twig/twig": "^3.3.8", - "fyrkat/openssl": "^1", - "fyrkat/oauth-server": "^1" + "fyrkat/openssl": "^1.2.1", + "fyrkat/oauth-server": "^1.2.1" }, "require-dev": { "vimeo/psalm": "^4.29", diff --git a/composer.lock b/composer.lock index 8d3f92c..b62ec76 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ec722fb651f422c40425601f31655d69", + "content-hash": "00d19eb57b030b0d907e9004f154aebd", "packages": [ { "name": "fyrkat/oauth-server", @@ -37,11 +37,11 @@ }, { "name": "fyrkat/openssl", - "version": "v1.3.1", + "version": "v1.2.1", "source": { "type": "git", "url": "https://git.sr.ht/~jornane/php-openssl", - "reference": "4ba36f2e080b7374ef50ce721beaa502021ef02c" + "reference": "34ef596055287c72a110e98f0467aeb5f5386c65" }, "require": { "ext-openssl": "*", @@ -64,20 +64,20 @@ } ], "description": "Class wrappers for PHPs built-in openssl_* functions", - "time": "2023-11-06T09:19:12+00:00" + "time": "2023-02-12T22:26:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { @@ -92,7 +92,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -130,7 +130,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" }, "funding": [ { @@ -146,20 +146,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { @@ -174,7 +174,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -213,7 +213,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" }, "funding": [ { @@ -229,20 +229,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "twig/twig", - "version": "v3.4.3", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58" + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58", - "reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", "shasum": "" }, "require": { @@ -257,7 +257,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "3.5-dev" } }, "autoload": { @@ -293,7 +293,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.4.3" + "source": "https://github.com/twigphp/Twig/tree/v3.5.1" }, "funding": [ { @@ -305,7 +305,7 @@ "type": "tidelift" } ], - "time": "2022-09-28T08:42:51+00:00" + "time": "2023-02-08T07:49:20+00:00" } ], "packages-dev": [ @@ -550,30 +550,30 @@ }, { "name": "composer/pcre", - "version": "1.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/composer/pcre.git", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560" + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/67a32d7d6f9f560b726ab25a061b38ff3a80c560", - "reference": "67a32d7d6f9f560b726ab25a061b38ff3a80c560", + "url": "https://api.github.com/repos/composer/pcre/zipball/4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", + "reference": "4bff79ddd77851fe3cdd11616ed3f92841ba5bd2", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "phpstan/phpstan": "^1.3", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5" + "symfony/phpunit-bridge": "^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -601,7 +601,7 @@ ], "support": { "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/1.0.1" + "source": "https://github.com/composer/pcre/tree/3.1.0" }, "funding": [ { @@ -617,7 +617,7 @@ "type": "tidelift" } ], - "time": "2022-01-21T20:24:37+00:00" + "time": "2022-11-17T09:50:14+00:00" }, { "name": "composer/semver", @@ -702,27 +702,27 @@ }, { "name": "composer/xdebug-handler", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a" + "reference": "ced299686f41dce890debac69273b47ffe98a40c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/9e36aeed4616366d2b690bdce11f71e9178c579a", - "reference": "9e36aeed4616366d2b690bdce11f71e9178c579a", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", + "reference": "ced299686f41dce890debac69273b47ffe98a40c", "shasum": "" }, "require": { - "composer/pcre": "^1", - "php": "^5.3.2 || ^7.0 || ^8.0", + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", "psr/log": "^1 || ^2 || ^3" }, "require-dev": { "phpstan/phpstan": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^4.2 || ^5.0 || ^6.0" + "symfony/phpunit-bridge": "^6.0" }, "type": "library", "autoload": { @@ -748,7 +748,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/2.0.5" + "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" }, "funding": [ { @@ -764,7 +764,7 @@ "type": "tidelift" } ], - "time": "2022-02-24T20:20:32+00:00" + "time": "2022-02-25T21:32:43+00:00" }, { "name": "dnoegel/php-xdg-base-dir", @@ -805,32 +805,35 @@ }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", + "reference": "e157ef3f3124bbf6fe7ce0ffd109e8a8ef284e7f", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^2 || ^3", "ext-tokenizer": "*", - "php": "^7.1 || ^8.0", + "php": "^7.2 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { - "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", + "doctrine/cache": "^2.0", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^5.4 || ^6", "vimeo/psalm": "^4.10" }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", "autoload": { "psr-4": { @@ -872,36 +875,79 @@ ], "support": { "issues": "https://github.com/doctrine/annotations/issues", - "source": "https://github.com/doctrine/annotations/tree/1.13.3" + "source": "https://github.com/doctrine/annotations/tree/2.0.1" }, - "time": "2022-07-02T10:48:51+00:00" + "time": "2023-02-02T22:02:53+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -928,7 +974,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -944,35 +990,37 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", + "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1004,7 +1052,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/1.2.3" + "source": "https://github.com/doctrine/lexer/tree/2.1.0" }, "funding": [ { @@ -1020,7 +1068,7 @@ "type": "tidelift" } ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2022-12-14T08:49:07+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -1125,52 +1173,53 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.4.0", + "version": "v3.14.4", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad" + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", - "reference": "47177af1cfb9dab5d1cc4daf91b7179c2efe7fad", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/1b3d9dba63d93b8a202c31e824748218781eae6b", + "reference": "1b3d9dba63d93b8a202c31e824748218781eae6b", "shasum": "" }, "require": { - "composer/semver": "^3.2", - "composer/xdebug-handler": "^2.0", - "doctrine/annotations": "^1.12", + "composer/semver": "^3.3", + "composer/xdebug-handler": "^3.0.3", + "doctrine/annotations": "^2", + "doctrine/lexer": "^2 || ^3", "ext-json": "*", "ext-tokenizer": "*", - "php": "^7.2.5 || ^8.0", - "php-cs-fixer/diff": "^2.0", - "symfony/console": "^4.4.20 || ^5.1.3 || ^6.0", - "symfony/event-dispatcher": "^4.4.20 || ^5.0 || ^6.0", - "symfony/filesystem": "^4.4.20 || ^5.0 || ^6.0", - "symfony/finder": "^4.4.20 || ^5.0 || ^6.0", - "symfony/options-resolver": "^4.4.20 || ^5.0 || ^6.0", - "symfony/polyfill-mbstring": "^1.23", - "symfony/polyfill-php80": "^1.23", - "symfony/polyfill-php81": "^1.23", - "symfony/process": "^4.4.20 || ^5.0 || ^6.0", - "symfony/stopwatch": "^4.4.20 || ^5.0 || ^6.0" + "php": "^7.4 || ^8.0", + "sebastian/diff": "^4.0 || ^5.0", + "symfony/console": "^5.4 || ^6.0", + "symfony/event-dispatcher": "^5.4 || ^6.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/finder": "^5.4 || ^6.0", + "symfony/options-resolver": "^5.4 || ^6.0", + "symfony/polyfill-mbstring": "^1.27", + "symfony/polyfill-php80": "^1.27", + "symfony/polyfill-php81": "^1.27", + "symfony/process": "^5.4 || ^6.0", + "symfony/stopwatch": "^5.4 || ^6.0" }, "require-dev": { "justinrainbow/json-schema": "^5.2", - "keradus/cli-executor": "^1.5", - "mikey179/vfsstream": "^1.6.8", - "php-coveralls/php-coveralls": "^2.5.2", + "keradus/cli-executor": "^2.0", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.5.3", "php-cs-fixer/accessible-object": "^1.1", "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.2", "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.2.1", - "phpspec/prophecy": "^1.15", - "phpspec/prophecy-phpunit": "^1.1 || ^2.0", - "phpunit/phpunit": "^8.5.21 || ^9.5", - "phpunitgoodpractices/polyfill": "^1.5", - "phpunitgoodpractices/traits": "^1.9.1", - "symfony/phpunit-bridge": "^5.2.4 || ^6.0", - "symfony/yaml": "^4.4.20 || ^5.0 || ^6.0" + "phpspec/prophecy": "^1.16", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "phpunitgoodpractices/polyfill": "^1.6", + "phpunitgoodpractices/traits": "^1.9.2", + "symfony/phpunit-bridge": "^6.2.3", + "symfony/yaml": "^5.4 || ^6.0" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -1201,8 +1250,8 @@ ], "description": "A tool to automatically fix PHP code style", "support": { - "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", - "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.4.0" + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.14.4" }, "funding": [ { @@ -1210,7 +1259,7 @@ "type": "github" } ], - "time": "2021-12-11T16:25:08+00:00" + "time": "2023-02-09T21:49:13+00:00" }, { "name": "microsoft/tolerant-php-parser", @@ -1318,16 +1367,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.0.0", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d" + "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", - "reference": "8bbc021a8edb2e4a7ea2f8ad4fa9ec9dce2fcb8d", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", + "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", "shasum": "" }, "require": { @@ -1363,22 +1412,22 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.0.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0" }, - "time": "2020-12-01T19:48:11+00:00" + "time": "2022-12-08T20:46:14+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.1", + "version": "v4.15.3", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900" + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", - "reference": "0ef6c55a3f47f89d7a374e6f835197a0b5fcf900", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039", + "reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039", "shasum": "" }, "require": { @@ -1419,9 +1468,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3" }, - "time": "2022-09-04T07:30:47+00:00" + "time": "2023-01-16T22:05:37+00:00" }, { "name": "openlss/lib-array2xml", @@ -1666,59 +1715,6 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "php-cs-fixer/diff", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/diff.git", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", - "symfony/process": "^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" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "sebastian/diff v3 backport support for PHP 5.6+", - "homepage": "https://github.com/PHP-CS-Fixer", - "keywords": [ - "diff" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/diff/issues", - "source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2" - }, - "abandoned": true, - "time": "2020-10-14T08:32:19+00:00" - }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -1831,25 +1827,30 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.6.1", + "version": "1.6.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/48f445a408c131e38cab1c235aa6d2bb7a0bb20d", + "reference": "48f445a408c131e38cab1c235aa6d2bb7a0bb20d", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.0" }, "require-dev": { "ext-tokenizer": "*", - "psalm/phar": "^4.8" + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-phpunit": "^1.1", + "phpunit/phpunit": "^9.5", + "rector/rector": "^0.13.9", + "vimeo/psalm": "^4.25" }, "type": "library", "extra": { @@ -1875,22 +1876,22 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.2" }, - "time": "2022-03-15T21:29:03+00:00" + "time": "2022-10-14T12:47:21+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.17", + "version": "9.2.24", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8" + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/aa94dc41e8661fe90c7316849907cba3007b10d8", - "reference": "aa94dc41e8661fe90c7316849907cba3007b10d8", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2cf940ebc6355a9d430462811b5aaa308b174bed", + "reference": "2cf940ebc6355a9d430462811b5aaa308b174bed", "shasum": "" }, "require": { @@ -1946,7 +1947,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.17" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.24" }, "funding": [ { @@ -1954,7 +1955,7 @@ "type": "github" } ], - "time": "2022-08-30T12:24:04+00:00" + "time": "2023-01-26T08:26:55+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2199,20 +2200,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.6.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7b1615e3e887d6c719121c6d4a44b0ab9645555", + "reference": "e7b1615e3e887d6c719121c6d4a44b0ab9645555", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -2250,7 +2251,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -2281,7 +2282,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.3" }, "funding": [ { @@ -2297,7 +2298,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2023-02-04T13:37:15+00:00" }, { "name": "psr/cache", @@ -2350,20 +2351,20 @@ }, { "name": "psr/container", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/php-fig/container.git", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", - "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea", + "reference": "513e0666f7216c7459170d56df27dfcefe1689ea", "shasum": "" }, "require": { - "php": ">=7.2.0" + "php": ">=7.4.0" }, "type": "library", "autoload": { @@ -2392,9 +2393,9 @@ ], "support": { "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/1.1.1" + "source": "https://github.com/php-fig/container/tree/1.1.2" }, - "time": "2021-03-05T17:36:06+00:00" + "time": "2021-11-05T16:50:12+00:00" }, { "name": "psr/event-dispatcher", @@ -2928,16 +2929,16 @@ }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "5.1.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", + "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", "shasum": "" }, "require": { @@ -2979,7 +2980,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" }, "funding": [ { @@ -2987,7 +2988,7 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-02-03T06:03:51+00:00" }, { "name": "sebastian/exporter", @@ -3301,16 +3302,16 @@ }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "4.0.5", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", + "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", "shasum": "" }, "require": { @@ -3349,10 +3350,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" }, "funding": [ { @@ -3360,7 +3361,7 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2023-02-03T06:07:39+00:00" }, { "name": "sebastian/resource-operations", @@ -3419,16 +3420,16 @@ }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", + "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", "shasum": "" }, "require": { @@ -3463,7 +3464,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" }, "funding": [ { @@ -3471,7 +3472,7 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T06:13:03+00:00" }, { "name": "sebastian/version", @@ -3528,16 +3529,16 @@ }, { "name": "symfony/console", - "version": "v5.4.14", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d" + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/984ea2c0f45f42dfed01d2f3987b187467c4b16d", - "reference": "984ea2c0f45f42dfed01d2f3987b187467c4b16d", + "url": "https://api.github.com/repos/symfony/console/zipball/dccb8d251a9017d5994c988b034d3e18aaabf740", + "reference": "dccb8d251a9017d5994c988b034d3e18aaabf740", "shasum": "" }, "require": { @@ -3607,7 +3608,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.14" + "source": "https://github.com/symfony/console/tree/v5.4.19" }, "funding": [ { @@ -3623,7 +3624,7 @@ "type": "tidelift" } ], - "time": "2022-10-07T08:01:20+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3694,16 +3695,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v5.4.9", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc" + "reference": "abf49cc084c087d94b4cb939c3f3672971784e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", - "reference": "8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/abf49cc084c087d94b4cb939c3f3672971784e0c", + "reference": "abf49cc084c087d94b4cb939c3f3672971784e0c", "shasum": "" }, "require": { @@ -3759,7 +3760,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.9" + "source": "https://github.com/symfony/event-dispatcher/tree/v5.4.19" }, "funding": [ { @@ -3775,7 +3776,7 @@ "type": "tidelift" } ], - "time": "2022-05-05T16:45:39+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3858,16 +3859,16 @@ }, { "name": "symfony/filesystem", - "version": "v5.4.13", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51" + "reference": "648bfaca6a494f3e22378123bcee2894045dc9d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/ac09569844a9109a5966b9438fc29113ce77cf51", - "reference": "ac09569844a9109a5966b9438fc29113ce77cf51", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/648bfaca6a494f3e22378123bcee2894045dc9d8", + "reference": "648bfaca6a494f3e22378123bcee2894045dc9d8", "shasum": "" }, "require": { @@ -3902,7 +3903,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v5.4.13" + "source": "https://github.com/symfony/filesystem/tree/v5.4.19" }, "funding": [ { @@ -3918,20 +3919,20 @@ "type": "tidelift" } ], - "time": "2022-09-21T19:53:16+00:00" + "time": "2023-01-14T19:14:44+00:00" }, { "name": "symfony/finder", - "version": "v5.4.11", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c" + "reference": "6071aebf810ad13fe8200c224f36103abb37cf1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7872a66f57caffa2916a584db1aa7f12adc76f8c", - "reference": "7872a66f57caffa2916a584db1aa7f12adc76f8c", + "url": "https://api.github.com/repos/symfony/finder/zipball/6071aebf810ad13fe8200c224f36103abb37cf1f", + "reference": "6071aebf810ad13fe8200c224f36103abb37cf1f", "shasum": "" }, "require": { @@ -3965,7 +3966,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.11" + "source": "https://github.com/symfony/finder/tree/v5.4.19" }, "funding": [ { @@ -3981,20 +3982,20 @@ "type": "tidelift" } ], - "time": "2022-07-29T07:37:50+00:00" + "time": "2023-01-14T19:14:44+00:00" }, { "name": "symfony/options-resolver", - "version": "v5.4.11", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690" + "reference": "b03c99236445492f20c61666e8f7e5d388b078e5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/54f14e36aa73cb8f7261d7686691fd4d75ea2690", - "reference": "54f14e36aa73cb8f7261d7686691fd4d75ea2690", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/b03c99236445492f20c61666e8f7e5d388b078e5", + "reference": "b03c99236445492f20c61666e8f7e5d388b078e5", "shasum": "" }, "require": { @@ -4034,7 +4035,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.11" + "source": "https://github.com/symfony/options-resolver/tree/v5.4.19" }, "funding": [ { @@ -4050,20 +4051,20 @@ "type": "tidelift" } ], - "time": "2022-07-20T13:00:38+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "433d05519ce6990bf3530fba6957499d327395c2" + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/433d05519ce6990bf3530fba6957499d327395c2", - "reference": "433d05519ce6990bf3530fba6957499d327395c2", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", "shasum": "" }, "require": { @@ -4075,7 +4076,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4115,7 +4116,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" }, "funding": [ { @@ -4131,20 +4132,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", - "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", "shasum": "" }, "require": { @@ -4156,7 +4157,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4199,7 +4200,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" }, "funding": [ { @@ -4215,20 +4216,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85" + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/e440d35fa0286f77fb45b79a03fedbeda9307e85", - "reference": "e440d35fa0286f77fb45b79a03fedbeda9307e85", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", "shasum": "" }, "require": { @@ -4237,7 +4238,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4278,7 +4279,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" }, "funding": [ { @@ -4294,20 +4295,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { @@ -4316,7 +4317,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4361,7 +4362,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" }, "funding": [ { @@ -4377,20 +4378,20 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/polyfill-php81", - "version": "v1.26.0", + "version": "v1.27.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1" + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1", - "reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/707403074c8ea6e2edaf8794b0157a0bfa52157a", + "reference": "707403074c8ea6e2edaf8794b0157a0bfa52157a", "shasum": "" }, "require": { @@ -4399,7 +4400,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -4440,7 +4441,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.27.0" }, "funding": [ { @@ -4456,20 +4457,20 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { "name": "symfony/process", - "version": "v5.4.11", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + "reference": "c5ba874c9b636dbccf761e22ce750e88ec3f55e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "url": "https://api.github.com/repos/symfony/process/zipball/c5ba874c9b636dbccf761e22ce750e88ec3f55e1", + "reference": "c5ba874c9b636dbccf761e22ce750e88ec3f55e1", "shasum": "" }, "require": { @@ -4502,7 +4503,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.11" + "source": "https://github.com/symfony/process/tree/v5.4.19" }, "funding": [ { @@ -4518,7 +4519,7 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/service-contracts", @@ -4605,16 +4606,16 @@ }, { "name": "symfony/stopwatch", - "version": "v5.4.13", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "6df7a3effde34d81717bbef4591e5ffe32226d69" + "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6df7a3effde34d81717bbef4591e5ffe32226d69", - "reference": "6df7a3effde34d81717bbef4591e5ffe32226d69", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/bd2b066090fd6a67039371098fa25a84cb2679ec", + "reference": "bd2b066090fd6a67039371098fa25a84cb2679ec", "shasum": "" }, "require": { @@ -4647,7 +4648,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.13" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.19" }, "funding": [ { @@ -4663,20 +4664,20 @@ "type": "tidelift" } ], - "time": "2022-09-28T13:19:49+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "symfony/string", - "version": "v5.4.14", + "version": "v5.4.19", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "089e7237497fae7a9c404d0c3aeb8db3254733e4" + "reference": "0a01071610fd861cc160dfb7e2682ceec66064cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/089e7237497fae7a9c404d0c3aeb8db3254733e4", - "reference": "089e7237497fae7a9c404d0c3aeb8db3254733e4", + "url": "https://api.github.com/repos/symfony/string/zipball/0a01071610fd861cc160dfb7e2682ceec66064cb", + "reference": "0a01071610fd861cc160dfb7e2682ceec66064cb", "shasum": "" }, "require": { @@ -4733,7 +4734,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.14" + "source": "https://github.com/symfony/string/tree/v5.4.19" }, "funding": [ { @@ -4749,7 +4750,7 @@ "type": "tidelift" } ], - "time": "2022-10-05T15:16:54+00:00" + "time": "2023-01-01T08:32:19+00:00" }, { "name": "theseer/tokenizer", @@ -4865,16 +4866,16 @@ }, { "name": "vimeo/psalm", - "version": "4.29.0", + "version": "4.30.0", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3" + "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3", - "reference": "7ec5ffbd5f68ae03782d7fd33fff0c45a69f95b3", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/d0bc6e25d89f649e4f36a534f330f8bb4643dd69", + "reference": "d0bc6e25d89f649e4f36a534f330f8bb4643dd69", "shasum": "" }, "require": { @@ -4967,9 +4968,9 @@ ], "support": { "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm/tree/4.29.0" + "source": "https://github.com/vimeo/psalm/tree/4.30.0" }, - "time": "2022-10-11T17:09:17+00:00" + "time": "2022-11-06T20:37:08+00:00" }, { "name": "webmozart/assert", @@ -5090,5 +5091,5 @@ "php": ">=7.3" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/src/letswifi/browserauth/simplesamlauth.php b/src/letswifi/browserauth/simplesamlauth.php index eb2f0c5..2f1e728 100644 --- a/src/letswifi/browserauth/simplesamlauth.php +++ b/src/letswifi/browserauth/simplesamlauth.php @@ -10,7 +10,6 @@ namespace letswifi\browserauth; -use DomainException; use Exception; use OutOfBoundsException; use Throwable; @@ -142,9 +141,6 @@ public function requireAuth(): string // we use the old value field accessor. $nameID = $this->as->getAuthData( 'saml:sp:NameID' ); - if ( null === $nameID ) { - throw new DomainException( 'NameID not present in SAML assertion' ); - } if ( \method_exists( $nameID, 'getValue' ) ) { return $nameID->getValue(); } diff --git a/src/letswifi/letswifiapp.php b/src/letswifi/letswifiapp.php index d283217..9a1a875 100644 --- a/src/letswifi/letswifiapp.php +++ b/src/letswifi/letswifiapp.php @@ -23,6 +23,8 @@ use fyrkat\oauth\token\RefreshToken; use fyrkat\openssl\PKCS7; +use fyrkat\openssl\PrivateKey; +use fyrkat\openssl\X509; use letswifi\browserauth\BrowserAuthInterface; @@ -191,20 +193,31 @@ public function getProfileSigner(): ?PKCS7 } else { throw new RuntimeException( 'File specified in signing.cert does not exist, please migrate to profile.signing.cert' ); } + + $signingKey = $signingCert; } else { $signingCert = $this->config->getStringOrNull( 'profile.signing.cert' ); - $signingKey = $this->config->getStringOrNull( 'profile.signing.key' ); - if ( null !== $signingCert && null !== $signingKey ) { - $signingCert .= "\n${signingKey}"; - } + $signingKey = $this->config->getStringOrNull( 'profile.signing.key' ) ?? $signingCert; } - if ( null === $signingCert ) { + if ( null === $signingKey || null === $signingCert ) { return null; } $passphrase = $this->config->getStringOrNull( 'profile.signing.passphrase' ); - return PKCS7::readChainPEM( $signingCert, $passphrase ); + $signingCA = $this->config->getArrayOrEmpty( 'profile.signing.ca' ); + if ( null !== $signingCA ) { + $signingCAChain = []; + foreach ( $signingCA as $ca) { + $signingCAChain[] = new X509($ca); + } + $signingCA = $signingCAChain; + } + + $certificate = new X509( $signingCert ); + $key = new PrivateKey( $signingKey, $passphrase ); + + return new PKCS7( $certificate, $key , $signingCA); } /** diff --git a/src/letswifi/profile/auth/tlsauth.php b/src/letswifi/profile/auth/tlsauth.php index 1a16cb4..9a870a3 100644 --- a/src/letswifi/profile/auth/tlsauth.php +++ b/src/letswifi/profile/auth/tlsauth.php @@ -20,15 +20,20 @@ class TlsAuth extends AbstractAuth /** @var ?PKCS12 */ private $pkcs12; + /** @var string */ + private $passphrase; + /** * @param array $caCertificates Trusted CA certificates * @param array $serverNames Accepted server names * @param ?PKCS12 $pkcs12 Certificate for user/device authentication + * @param ?string $passphrase Transient password to be used for encrypting the PKCS12 payload */ - public function __construct( array $caCertificates, array $serverNames, ?PKCS12 $pkcs12 ) + public function __construct( array $caCertificates, array $serverNames, ?PKCS12 $pkcs12, ?string $passphrase = null ) { parent::__construct( $caCertificates, $serverNames ); $this->pkcs12 = $pkcs12; + $this->passphrase = $passphrase ?? 'pkcs12'; } public function getExpiry(): ?DateTimeInterface @@ -41,6 +46,11 @@ public function getPKCS12(): ?PKCS12 return $this->pkcs12; } + public function getPassphrase(): string + { + return $this->passphrase; + } + public function getIdentity(): ?string { $pkcs12 = $this->getPKCS12(); diff --git a/src/letswifi/profile/generator/abstractgenerator.php b/src/letswifi/profile/generator/abstractgenerator.php index 7898516..d43cf01 100644 --- a/src/letswifi/profile/generator/abstractgenerator.php +++ b/src/letswifi/profile/generator/abstractgenerator.php @@ -30,23 +30,16 @@ abstract class AbstractGenerator implements Generator */ protected $authenticationMethods; - /** - * Passphrase to encrypt the profile with - * - * @var ?string */ - protected $passphrase; - /** * Create a new generator. * * @param IProfileData $profileData Profile data * @param array $authenticationMethods Authentication methods */ - public function __construct( IProfileData $profileData, array $authenticationMethods, ?string $passphrase = null ) + public function __construct( IProfileData $profileData, array $authenticationMethods ) { $this->profileData = $profileData; $this->authenticationMethods = $authenticationMethods; - $this->passphrase = $passphrase; } public function getFilename(): string diff --git a/src/letswifi/profile/generator/eapconfiggenerator.php b/src/letswifi/profile/generator/eapconfiggenerator.php index 16bce30..ad515fe 100644 --- a/src/letswifi/profile/generator/eapconfiggenerator.php +++ b/src/letswifi/profile/generator/eapconfiggenerator.php @@ -14,10 +14,13 @@ use DateTimeInterface; use DateTimeZone; use InvalidArgumentException; + use letswifi\profile\auth\AbstractAuth; use letswifi\profile\auth\Auth; + use letswifi\profile\Helpdesk; use letswifi\profile\Location; + use letswifi\profile\network\HS20Network; use letswifi\profile\network\Network; use letswifi\profile\network\SSIDNetwork; @@ -32,7 +35,8 @@ public function generate(): string $result = ''; $result .= '' . "\r\n" . '' - . "\r\n\t" . ''; + . "\r\n\t" . '' + ; if ( null !== $expiry = $this->getExpiry() ) { $expiry = new DateTimeImmutable( '@' . $expiry->getTimestamp(), new DateTimeZone( 'UTC' ) ); $expiryString = $expiry->format( 'Y-m-d\\TH:i:s\\Z' ); @@ -40,38 +44,46 @@ public function generate(): string /** @psalm-suppress RedundantConditionGivenDocblockType */ \assert( false !== $expiryString ); $result .= '' - . "\r\n\t\t" . '' . static::e( $expiryString ) . ''; + . "\r\n\t\t" . '' . static::e( $expiryString ) . '' + ; } $result .= '' - . "\r\n\t\t" . ''; + . "\r\n\t\t" . '' + ; foreach ( $this->authenticationMethods as $authentication ) { - $result .= $this->generateAuthenticationMethodXml( $authentication ); + $result .= static::generateAuthenticationMethodXml( $authentication ); } $result .= '' . "\r\n\t\t" . '' - . "\r\n\t\t" . ''; + . "\r\n\t\t" . '' + ; foreach ( $this->profileData->getNetworks() as $network ) { $result .= static::generateNetworkXml( $network ); } $result .= '' . "\r\n\t\t" . '' . "\r\n\t\t" . '' - . "\r\n\t\t\t" . '' . static::e( $this->profileData->getDisplayName() ) . ''; + . "\r\n\t\t\t" . '' . static::e( $this->profileData->getDisplayName() ) . '' + ; if ( null !== $description = $this->profileData->getDescription() ) { $result .= '' - . "\r\n\t\t\t" . '' . static::e( $description ) . ''; + . "\r\n\t\t\t" . '' . static::e( $description ) . '' + ; } if ( null !== $loc = $this->profileData->getProviderLocation() ) { $result .= '' - . "\r\n\t\t\t" . '' . static::generateLocationXml( $loc ) . ''; + . "\r\n\t\t\t" . '' . static::generateLocationXml( $loc ) . '' + ; } if ( null !== $logo = $this->profileData->getProviderLogo() ) { $result .= '' - . "\r\n\t\t\t" . '' . \base64_encode( $logo->getBytes() ) . ''; + . "\r\n\t\t\t" . '' . \base64_encode( $logo->getBytes() ) . '' + ; } if ( null !== $tos = $this->profileData->getTermsOfUse() ) { $result .= '' - . "\r\n\t\t\t" . '' . static::e( $tos ) . ''; + . "\r\n\t\t\t" . '' . static::e( $tos ) . '' + ; } if ( null !== $helpdesk = $this->profileData->getHelpDesk() ) { $result .= static::generateHelpdeskXml( $helpdesk ); @@ -129,7 +141,8 @@ private static function generateHS20NetworkXml( HS20Network $network ): string return '' . "\r\n\t\t\t" . '' . "\r\n\t\t\t\t" . '' . static::e( $network->getConsortiumOID() ) . '' - . "\r\n\t\t\t" . ''; + . "\r\n\t\t\t" . '' + ; } private static function generateSSIDNetworkXml( SSIDNetwork $network ): string @@ -138,13 +151,14 @@ private static function generateSSIDNetworkXml( SSIDNetwork $network ): string . "\r\n\t\t\t" . '' . "\r\n\t\t\t\t" . '' . static::e( $network->getSsid() ) . '' . "\r\n\t\t\t\t" . '' . static::e( $network->getMinRSNProto() ) . '' - . "\r\n\t\t\t" . ''; + . "\r\n\t\t\t" . '' + ; } - private function generateAuthenticationMethodXml( Auth $authenticationMethod ): string + private static function generateAuthenticationMethodXml( Auth $authenticationMethod ): string { if ( $authenticationMethod instanceof \letswifi\profile\auth\TlsAuth ) { - return $this->generateTlsAuthenticationMethodXml( $authenticationMethod ); + return static::generateTlsAuthenticationMethodXml( $authenticationMethod ); } throw new InvalidArgumentException( 'Unsupported authentication method: ' . \get_class( $authenticationMethod ) ); @@ -157,16 +171,11 @@ private function generateAuthenticationMethodXml( Auth $authenticationMethod ): * * @return string XML portion for wifi and certificates, to be used in a EAP config file */ - private function generateTlsAuthenticationMethodXml( \letswifi\profile\auth\TlsAuth $authenticationMethod ): string + private static function generateTlsAuthenticationMethodXml( \letswifi\profile\auth\TlsAuth $authenticationMethod ): string { $identity = $authenticationMethod->getIdentity(); $pkcs12 = $authenticationMethod->getPKCS12(); - $defaultPassphrase = 'pkcs12'; - - if ( null !== $pkcs12 ) { - // We need 3DES support, since some of our supported clients support nothing else - $pkcs12 = $pkcs12->use3des(); - } + $passphrase = $authenticationMethod->getPassphrase(); $result = ''; $result .= '' @@ -174,23 +183,29 @@ private function generateTlsAuthenticationMethodXml( \letswifi\profile\auth\TlsA . "\r\n\t\t\t\t" . '' . "\r\n\t\t\t\t\t" . '13' . "\r\n\t\t\t\t" . '' - . "\r\n\t\t\t\t" . ''; + . "\r\n\t\t\t\t" . '' + ; foreach ( $authenticationMethod->getServerCACertificates() as $ca ) { $result .= '' - . "\r\n\t\t\t\t\t" . '' . AbstractAuth::pemToBase64Der( $ca->getX509Pem() ) . ''; + . "\r\n\t\t\t\t\t" . '' . AbstractAuth::pemToBase64Der( $ca->getX509Pem() ) . '' + ; } foreach ( $authenticationMethod->getServerNames() as $serverName ) { $result .= '' - . "\r\n\t\t\t\t\t" . '' . static::e( $serverName ) . ''; + . "\r\n\t\t\t\t\t" . '' . static::e( $serverName ) . '' + ; } $result .= '' - . "\r\n\t\t\t\t" . ''; + . "\r\n\t\t\t\t" . '' + ; if ( null === $authenticationMethod->getPKCS12() ) { $result .= '' - . "\r\n\t\t\t\t" . ''; + . "\r\n\t\t\t\t" . '' + ; } else { $result .= '' - . "\r\n\t\t\t\t" . ''; + . "\r\n\t\t\t\t" . '' + ; if ( null !== $identity ) { // https://github.com/GEANT/CAT/blob/v2.0.3/devices/xml/eap-metadata.xsd // The schema specifies @@ -198,21 +213,20 @@ private function generateTlsAuthenticationMethodXml( \letswifi\profile\auth\TlsA // Expired draft specifices // cat.eduroam.org uses , so we do too $result .= '' - . "\r\n\t\t\t\t\t" . '' . static::e( $identity ) . ''; + . "\r\n\t\t\t\t\t" . '' . static::e( $identity ) . '' + ; } if ( null !== $pkcs12 ) { $result .= '' - . "\r\n\t\t\t\t" . '' . \base64_encode( $pkcs12->getPKCS12Bytes( $this->passphrase ?: $defaultPassphrase ) ) . ''; - if ( !$this->passphrase ) { - $result .= '' - . "\r\n\t\t\t\t" . '' . static::e( $defaultPassphrase ) . ''; - } - $result .= '' - . "\r\n\t\t\t\t" . ''; + . "\r\n\t\t\t\t" . '' . \base64_encode( $pkcs12->getPKCS12Bytes( $passphrase ) ) . '' + . "\r\n\t\t\t\t" . '' . static::e( $passphrase ) . '' + . "\r\n\t\t\t\t" . '' + ; } } $result .= '' - . "\r\n\t\t\t" . ''; + . "\r\n\t\t\t" . '' + ; return $result; } @@ -244,8 +258,9 @@ private static function generateLocationXml( Location $location ): string return '' . "\r\n" - . "\r\n{$lat}" - . "\r\n{$lon}" - . "\r\n"; + . "\r\n${lat}" + . "\r\n${lon}" + . "\r\n" + ; } } diff --git a/src/letswifi/profile/generator/mobileconfiggenerator.php b/src/letswifi/profile/generator/mobileconfiggenerator.php index afd0b42..c85423f 100644 --- a/src/letswifi/profile/generator/mobileconfiggenerator.php +++ b/src/letswifi/profile/generator/mobileconfiggenerator.php @@ -49,17 +49,12 @@ static function ( $a ) { return $a instanceof TlsAuth && null !== $a->getPKCS12( \assert( $tlsAuthMethod instanceof TlsAuth ); $tlsAuthMethodUuid = static::uuidgen(); - $defaultPassphrase = 'pkcs12'; + $passphrase = $tlsAuthMethod->getPassphrase(); if ( $pkcs12 = $tlsAuthMethod->getPKCS12() ) { // Remove the CA from the PKCS12 object, // because otherwise MacOS would trust that CA for HTTPS traffic $pkcs12 = new PKCS12( $pkcs12->getX509(), $pkcs12->getPrivateKey() ); } - if ( null !== $pkcs12 ) { - // We need 3DES support, since some of our supported clients support nothing else - $pkcs12 = $pkcs12->use3des(); - } - /** @var array<\fyrkat\openssl\X509> */ $caCertificates = \array_merge( $caCertificates, $tlsAuthMethod->getServerCACertificates() ); \assert( null !== $pkcs12 ); @@ -99,13 +94,9 @@ static function ( $a ) { return $a instanceof TlsAuth && null !== $a->getPKCS12( $result .= ' PayloadContent' . "\n" . ' ' . "\n" . ' ' - . "\n"; - if ( !$this->passphrase ) { - $result .= ' Password' - . "\n" . ' ' . static::e( $defaultPassphrase ) . '' - . "\n"; - } - $result .= ' PayloadUUID' + . "\n" . ' Password' + . "\n" . ' ' . static::e( $passphrase ) . '' + . "\n" . ' PayloadUUID' . "\n" . ' ' . static::e( $tlsAuthMethodUuid ) . '' . "\n" . ' PayloadIdentifier' . "\n" . ' ' . static::e( $identifier . '.' . $tlsAuthMethodUuid ) . '' @@ -115,7 +106,7 @@ static function ( $a ) { return $a instanceof TlsAuth && null !== $a->getPKCS12( . "\n" . ' ' . static::e( $pkcs12->getX509()->getSubject()->getCommonName() ) . '' . "\n" . ' PayloadContent' . "\n" . ' ' - . "\n" . ' ' . static::e( static::columnFormat( \base64_encode( $pkcs12->getPKCS12Bytes( $this->passphrase ?: $defaultPassphrase ) ), 52, 4 ) ) + . "\n" . ' ' . static::e( static::columnFormat( \base64_encode( $pkcs12->getPKCS12Bytes( $passphrase ) ), 52, 4 ) ) . "\n" . ' ' . "\n" . ' PayloadType' . "\n" . ' com.apple.security.pkcs12' @@ -254,7 +245,7 @@ static function ( $_ ){ return static::uuidgen(); }, $app = new LetsWifiApp(); if ( $signer = $app->getProfileSigner() ) { - $result = $signer->sign( $result ); + $result = $signer->binarySign( $result ); } return $result; diff --git a/src/letswifi/profile/generator/oncgenerator.php b/src/letswifi/profile/generator/oncgenerator.php deleted file mode 100644 index f20b12f..0000000 --- a/src/letswifi/profile/generator/oncgenerator.php +++ /dev/null @@ -1,238 +0,0 @@ - - * Copyright: 2020-2022, Paul Dekkers, SURF - * SPDX-License-Identifier: BSD-3-Clause - */ - -namespace letswifi\profile\generator; - -use fyrkat\openssl\PKCS12; -use fyrkat\openssl\X509; - -use InvalidArgumentException; - -use letswifi\profile\auth\TlsAuth; -use letswifi\profile\network\Network; -use letswifi\profile\network\SSIDNetwork; - -use RuntimeException; - -class ONCGenerator extends AbstractGenerator -{ - public const PBKDF2_ITERATIONS = 20000; - - /** - * Generate the onc profile - */ - public function generate(): string - { - $payload = $this->generatePayload(); - if ($this->passphrase) { - $payload = \json_encode( - $payload, - \JSON_UNESCAPED_SLASHES | \JSON_THROW_ON_ERROR, - ); - \assert( \is_string( $payload ) ); - $payload = $this->encrypt( $payload, $this->passphrase ); - } - - return \json_encode( - $payload, - \JSON_UNESCAPED_SLASHES | \JSON_PRETTY_PRINT | \JSON_THROW_ON_ERROR, - ) . "\n"; - } - - public function getContentType(): string - { - return 'application/x-onc'; - } - - public function getFileExtension(): string - { - return 'onc'; - } - - protected static function encrypt( string $clearText, string $passphrase ): array - { - // TODO see if hmac can use something stronger than SHA1 - - $salt = \random_bytes( 12 ); - $key = \hash_pbkdf2('sha1', $passphrase, $salt, self::PBKDF2_ITERATIONS, 32, true); - $iv = \random_bytes(16); - $cipherText = \openssl_encrypt($clearText, 'AES-256-CBC', $key, \OPENSSL_RAW_DATA, $iv); - if ( false === $cipherText ) { - throw new RuntimeException( 'Unable to encrypt profile' ); - } - $hmac = \hash_hmac('sha1', $cipherText, $key, true); - - return [ - 'Cipher' => 'AES256', - 'Ciphertext' => \base64_encode( $cipherText ), - 'HMAC' => \base64_encode($hmac), - 'HMACMethod' => 'SHA1', - 'Salt' => \base64_encode($salt), - 'Stretch' => 'PBKDF2', - 'Iterations' => self::PBKDF2_ITERATIONS, - 'IV' => \base64_encode($iv), - 'Type' => 'EncryptedConfiguration', - ]; - } - - /** - * @return array - */ - protected function generatePayload(): array - { - $tlsAuthMethods = \array_filter( - $this->authenticationMethods, - static function ($a): bool { return $a instanceof TlsAuth && null !== $a->getPKCS12(); }, - ); - if ( 1 !== \count( $tlsAuthMethods ) ) { - throw new InvalidArgumentException( 'Expected 1 TLS auth method, got ' . \count( $tlsAuthMethods ) ); - } - $tlsAuthMethod = \reset( $tlsAuthMethods ); - /** @psalm-suppress RedundantCondition */ - \assert( $tlsAuthMethod instanceof TlsAuth ); - - $pkcs12 = $tlsAuthMethod->getPKCS12(); - \assert( null !== $pkcs12 ); // we already checked this when we created $tlsAuthMethods - // We need 3DES support, since some of our supported clients support nothing else - $pkcs12 = $pkcs12->use3des(); - - $caCertificates = $this->getCAPayloadFromAuthMethod( $tlsAuthMethod ); - $clientCertificate = $this->getClientCredentialPayloadFromAuthMethod( $pkcs12 ); - - $caIDs = \array_map( static function ( $certData ){return $certData['GUID']; }, $caCertificates ); - $clientCertID = $clientCertificate['GUID']; - $serverNames = $tlsAuthMethod->getServerNames(); - $serverSubjectMatch = $this->getLongestSuffix( ...$serverNames ); - - $clientCertCN = $pkcs12->getX509()->getSubject()->getCommonName(); - - $networkConfigurations = \array_filter( \array_map( function ( Network $network ) use ($clientCertID, $clientCertCN, $caIDs, $serverSubjectMatch) { - return $this->generateNetworkConfiguration( $network, $clientCertID, $clientCertCN, $caIDs, $serverSubjectMatch ); - }, $this->profileData->getNetworks() ) ); - - return [ - 'Type' => 'UnencryptedConfiguration', - 'Certificates' => \array_merge( $caCertificates, [$clientCertificate] ), - 'NetworkConfigurations' => $networkConfigurations, - ]; - } - - /** - * @param array $caIDs - * - * @return ?array - */ - protected static function generateNetworkConfiguration( Network $network, string $clientCertID, string $clientCertCN, array $caIDs, string $serverSubjectMatch ): ?array - { - if (!($network instanceof SSIDNetwork )) { - return null; - } - - $uuid = static::uuidgen(); - - return [ - 'GUID' => $uuid, - 'Name' => 'eduroam', - 'ProxySettings' => [ - 'Type' => 'WPAD', - ], - 'Remove' => false, - 'Type' => 'WiFi', - 'WiFi' => [ - 'AutoConnect' => true, - 'EAP' => [ - 'ClientCertRef' => $clientCertID, - 'ClientCertType' => 'Ref', - 'Identity' => $clientCertCN, - 'Outer' => 'EAP-TLS', - 'SaveCredentials' => true, - 'ServerCARefs' => $caIDs, - 'SubjectMatch' => $serverSubjectMatch, - 'UseSystemCAs' => false, - ], - 'HiddenSSID' => false, - 'SSID' => $network->getSSID(), - 'Security' => 'WPA-EAP', - ], - ]; - } - - /** - * @return array - */ - protected static function getCAPayloadFromAuthMethod( TlsAuth $authMethod ): array - { - return \array_map( static function (X509 $x509 ): array { - $uuid = static::uuidgen(); - - // writing as "\{$uuid\}" makes php-cs-fixer crash - return [ - 'GUID' => '{' . $uuid . '}', - 'Remove' => false, - 'Type' => 'Authority', - 'X509' => \base64_encode( $x509->getX509Der() ), - ]; - }, $authMethod->getServerCACertificates() ); - } - - /** - * @return array{GUID: string, Remove: false, Type: string, PKCS12: string} - */ - protected static function getClientCredentialPayloadFromAuthMethod( PKCS12 $pkcs12 ): array - { - // We use a PKCS12 without passphrase here. - // If $this->passphrase was set, we'll use that to encrypt the whole payload instead - $uuid = static::uuidgen(); - - return [ - 'GUID' => "[${uuid}]", - 'Remove' => false, - 'Type' => 'Client', - 'PKCS12' => \base64_encode( $pkcs12->getPKCS12Bytes( '' ) ), - ]; - } - - /** - * Get the longest common suffix domain components from a list of hostnames - * - * @param string $hostnames A list of host names - * - * @return string The longest common suffix for all given host names - */ - protected static function getLongestSuffix( string ...$hostnames ): string - { - if ( empty( $hostnames ) ) { - return ''; - } - if ( \count( $hostnames ) === 1) { - return \reset( $hostnames ); - } - $longest = $hostnames[0]; - foreach ( $hostnames as $candidate ) { - $pos = \strlen( $candidate ); - do { - $pos = (int)\strrpos( $candidate, '.', -1 * \strlen( $candidate ) + $pos - 1 ); - echo "'${longest}' ends with " . \substr( $candidate, $pos ) . "?\n"; - } while ( 0 < $pos && \str_ends_with( $longest, (string)\substr( $candidate, $pos ) ) ); - if ( !\str_ends_with( $longest, (string)\substr( $candidate, $pos ) ) ) { - $pos = \strpos( $candidate, '.', $pos + 1 ); - } - if ( false === $pos ) { - $longest = ''; - break; - } - if ( \str_ends_with( $longest, (string)\substr( $candidate, $pos ) ) ) { - $longest = (string)\substr( $candidate, 0 === $pos ? 0 : $pos + 1 ); - } - } - - return $longest; - } -} diff --git a/src/letswifi/profile/generator/pkcs12generator.php b/src/letswifi/profile/generator/pkcs12generator.php index 98c781d..5345f86 100644 --- a/src/letswifi/profile/generator/pkcs12generator.php +++ b/src/letswifi/profile/generator/pkcs12generator.php @@ -14,11 +14,25 @@ use letswifi\profile\auth\Auth; use letswifi\profile\auth\TlsAuth; +use letswifi\profile\IProfileData; use UnexpectedValueException; class PKCS12Generator extends AbstractGenerator { + /** @var string */ + protected $password; + + public function __construct( IProfileData $profileData, array $authenticationMethods, string $password = '' ) + { + parent::__construct( $profileData, $authenticationMethods ); + if ( empty($password) ) { + $this->password = 'pkcs12'; + } else { + $this->password = $password; + } + } + /** * Generate the eap-config profile */ @@ -37,8 +51,7 @@ static function ( Auth $a ): bool { return $a instanceof TlsAuth && null !== $a- \assert( $tlsAuthMethod instanceof TlsAuth ); if ( $pkcs12 = $tlsAuthMethod->getPKCS12() ) { - // We need 3DES support, since some of our supported clients support nothing else - return $pkcs12->use3des()->getPKCS12Bytes( $this->passphrase ?: '' ); + return $pkcs12->getPKCS12Bytes( $this->password ); } throw new UnexpectedValueException( 'Reached unreachable code; PKCS12 was null unexpectedly' ); diff --git a/src/letswifi/realm/realm.php b/src/letswifi/realm/realm.php index 53066c9..3c67e33 100644 --- a/src/letswifi/realm/realm.php +++ b/src/letswifi/realm/realm.php @@ -13,13 +13,13 @@ use DateInterval; use DateTimeImmutable; use DateTimeInterface; + use fyrkat\openssl\CSR; use fyrkat\openssl\DN; use fyrkat\openssl\OpenSSLConfig; use fyrkat\openssl\PKCS12; use fyrkat\openssl\PrivateKey; use fyrkat\openssl\X509; -use InvalidArgumentException; use letswifi\profile\auth\TlsAuth; @@ -50,14 +50,12 @@ public function __construct( RealmManager $manager, string $name ) * * @psalm-param class-string $generator The config generator to return * - * @param string $generator The config generator class to return - * @param User $user - * @param ?string $passphrase Passphrase to encrypt the profile with - * @param ?DateInterval $validity Period the profile will be valid for from generation + * @param string $generator The config generator class to return + * @param User $user * * @psalm-return T */ - public function getConfigGenerator( string $generator, User $user, ?string $passphrase = null, ?DateInterval $validity = null ): Generator + public function getConfigGenerator( string $generator, User $user, ?DateInterval $validity = null ): Generator { if ( null === $validity ) { $validity = $this->manager->getDefaultValidity( $this->name ); @@ -68,7 +66,7 @@ public function getConfigGenerator( string $generator, User $user, ?string $pass // TODO more generic method to get an arbitrary generator $pkcs12 = $this->generateClientCertificate( $user, $expiry ); - return new $generator( $this->getProfileData(), [$this->createAuthenticationMethod( $pkcs12 )], $passphrase ); + return new $generator( $this->getProfileData(), [$this->createAuthenticationMethod( $pkcs12 )] ); } /** @@ -96,14 +94,11 @@ public function getTrustedCaCertificates(): array /** * @param User $requester User requesting the certificate - * @param string $commonName Common name of the server certificate, must be a hostname + * @param string $commonName Common name of the server certificate * @param DateTimeInterface $expiry Expiry date */ public function generateServerCertificate( User $requester, string $commonName, DateTimeInterface $expiry ): PKCS12 { - if ( !\filter_var( $commonName, \FILTER_VALIDATE_DOMAIN | \FILTER_NULL_ON_FAILURE ) ) { - throw new InvalidArgumentException( 'Common name for a server certificate must be a hostname' ); - } $serverKey = new PrivateKey( new OpenSSLConfig( OpenSSLConfig::KEY_EC ) ); $dn = new DN( ['CN' => $commonName] ); $csr = CSR::generate( $dn, $serverKey ); @@ -111,7 +106,7 @@ public function generateServerCertificate( User $requester, string $commonName, $serial = $this->logPreparedServerCredential( $caCert, $requester, $csr, $expiry ); $caKey = $this->getSigningCAKey(); - $conf = new OpenSSLConfig( OpenSSLConfig::X509_SERVER + ['san' => 'DNS:' . $commonName] ); + $conf = new OpenSSLConfig( OpenSSLConfig::X509_SERVER ); $serverCert = $csr->sign( $caCert, $caKey, $expiry, $conf, $serial ); $this->logCompletedServerCredential( $requester, $serverCert ); diff --git a/tpl/app.html b/tpl/app.html index 799738a..5f05789 100644 --- a/tpl/app.html +++ b/tpl/app.html @@ -11,15 +11,12 @@

geteduroam– eduroam authentication made easy

  • {{ app.name }}
  • {% endfor %} -{% if os_config %} -
    -

    For some platforms there are no apps available. For these platforms, you can download a configuration profile that you can install directly using the operating systems facilities.

    + +

    For macOS, the current option is to install a .mobileconfig profile.

    -{% endif %} +
    Options for other platforms and professional users diff --git a/tpl/mobileconfig-mac-new.html b/tpl/mobileconfig-mac-new.html new file mode 100644 index 0000000..901e687 --- /dev/null +++ b/tpl/mobileconfig-mac-new.html @@ -0,0 +1,21 @@ +{% extends "dialog.html" %} + +{% block title %}Apple mobileconfig{% endblock %} + +{% block content %} +
    +

    geteduroam– eduroam authentication made easy

    +
    +

    Your download will begin shortly

    +

    +
    +

    If you have macOS Big Sur or newer, please go to Profiles in System Preferences to complete installation

    +
    + +{# We use a CSP with script-src: none, so this won't work: + +#} +{% endblock %} diff --git a/tpl/profile-advanced.html b/tpl/profile-advanced.html deleted file mode 100644 index 3100a54..0000000 --- a/tpl/profile-advanced.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "menu.html" %} - -{% block title %}New configuration profile{% endblock %} - -{% block content %} -
    -

    geteduroam– eduroam authentication made easy

    -

    For most users, the easiest way to use geteduroam is to use one of the official apps.

    - -
    -
    -

    For advanced users, and for using eduroam on a device where no app is yet available, - it is also possible to download a configuration profile. -

    - Encrypt my profile (advanced) -

    - Encrypting your profile requires you to enter the passphrase once when installing the profile. - Since the profile is usually installed rightaway after downloading it, - this will typically not lead to better security. -

    - However, there are some systems, usually aimed at professional users, - that will refuse to import a profile unless it has a passphrase. - Setting the passphrase here will thus make it easier to install - the profile on such systems. -


    -

    -
    -
      -{% for deviceName, deviceData in devices %} -
    • -{% endfor %} -
    -
    -
    -{% endblock %} diff --git a/tpl/profile-download.html b/tpl/profile-download.html deleted file mode 100644 index 66560d8..0000000 --- a/tpl/profile-download.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "dialog.html" %} - -{% block title %}Apple mobileconfig{% endblock %} - -{% block content %} -
    -

    geteduroam– eduroam authentication made easy

    -
    -

    Your download will begin shortly

    -
    - Download not starting? -

    - -
    -
    - -{% if passphrase %} -

    When prompted for a passphrase during installation, enter the following passphrase:

    -

    {{ passphrase }}

    -
    -{% endif %} - -{% if device == 'apple-mobileconfig' %} -

    If you have macOS Big Sur or newer, please go to Profiles in System Preferences to complete installation.

    -{% endif %} - -{% if device == 'google-onc' %} -

    After downloading the file, open the Chrome browser and browse to this URL: chrome://network. Then, use the Import ONC file button. The import is silent; the new network definitions will be added to the preferred networks.

    -{% endif %} - -
    - -{# We use a CSP with script-src: none, so this won't work: - -#} -{% endblock %} diff --git a/tpl/profiles-new.html b/tpl/profiles-new.html new file mode 100644 index 0000000..b269f23 --- /dev/null +++ b/tpl/profiles-new.html @@ -0,0 +1,23 @@ +{% extends "menu.html" %} + +{% block title %}New configuration profile{% endblock %} + +{% block content %} +
    +

    geteduroam– eduroam authentication made easy

    +

    For most users, the easiest way to use geteduroam is to use one of the official apps.

    + +
    +
    +

    For advanced users, and for using eduroam on a device where no app is yet available, + it is also possible to download a configuration profile +

      +{% for deviceName, deviceData in devices %} +
    • +{% endfor %} +
    +
    +
    +{% endblock %} diff --git a/www/app/index.php b/www/app/index.php index c28177e..87d0dc1 100644 --- a/www/app/index.php +++ b/www/app/index.php @@ -34,15 +34,8 @@ 'name' => 'Huawei', ], ], - 'os_config' => [ - 'mobileconfig' => [ - 'url' => "${basePath}/profiles/mac/", - 'name' => 'macOS', - ], - 'onc' => [ - 'url' => "${basePath}/profiles/onc/", - 'name' => 'ChromeOS', - ], + 'mobileconfig' => [ + 'url' => "${basePath}/profiles/mac/", ], 'manual' => [ 'url' => "${basePath}/profiles/new/", diff --git a/www/assets/geteduroam.css b/www/assets/geteduroam.css index 4c0988c..e2e6290 100644 --- a/www/assets/geteduroam.css +++ b/www/assets/geteduroam.css @@ -133,7 +133,7 @@ hr { } #loginform *:focus { - outline: none; + outline: none; } body{ @@ -190,6 +190,7 @@ main h1:first-child { ul.apps { padding: 0; margin: 0 auto; + max-width: 26em; } ul.apps li a.btn { min-width: 6em; @@ -201,12 +202,6 @@ ul.buttons li { margin-bottom: .5em; } -#profile-encrypt { - margin-bottom: 2em; -} -#profile-encrypt input, #passphrase-show { - font-size: 2em; -} @media (prefers-color-scheme: dark) { @@ -287,8 +282,8 @@ ul.buttons li { } .apps { - text-align: center; - text-align-last: center; + text-align: justify; + text-align-last: justify; } ul.apps li { display: inline; diff --git a/www/index.php b/www/index.php index 83b7ac4..af1b97e 100644 --- a/www/index.php +++ b/www/index.php @@ -26,5 +26,5 @@ $app->render( [ 'href' => "${basePath}/", - 'http://letswifi.app/api#v2' => $apiConfiguration, + 'http://letswifi.app/api#v1' => $apiConfiguration, ], 'info', $basePath ); diff --git a/www/profiles/mac/index.php b/www/profiles/mac/index.php index 4c5cba1..23ea9db 100644 --- a/www/profiles/mac/index.php +++ b/www/profiles/mac/index.php @@ -12,7 +12,34 @@ $basePath = '../..'; \assert( \array_key_exists( 'REQUEST_METHOD', $_SERVER ) ); -$downloadKind = 'apple-mobileconfig'; -$href = "${basePath}/profiles/mac/"; +$app = new letswifi\LetsWifiApp(); +$app->registerExceptionHandler(); +$realm = $app->getRealm(); +// just trigger a login +$app->getUserFromBrowserSession( $realm ); -require \implode(\DIRECTORY_SEPARATOR, [\dirname(__DIR__), 'new', '_download.php']); +// Create a short-lived cookie to allow the user ONE download without using POST +// If the download would fail, the user is still presented with a download button +// on this page, which uses a more reliable POST. +// If the meta_redirect would go through too late (after cookie expiry), +// the page being redirected to will also contain an appropriate download button. +\setcookie('mobileconfig-download-token', (string)\time(), [ + 'expires' => 0, // session cookie + 'httponly' => true, // not available in JavaScript + 'secure' => false, // we don't care, this is not for security, and this helps with local devving + 'path' => '/', // make it available to /profiles/new as well; relative path's don't work here so use "/" for now + 'samesite' => 'Strict', +]); + +switch ( $_SERVER['REQUEST_METHOD'] ) { + case 'GET': return $app->render( + [ + 'href' => "${basePath}/profiles/mac/", + 'action' => "${basePath}/profiles/new/", + 'device' => 'apple-mobileconfig', + 'meta_redirect' => "${basePath}/profiles/new/?" . \http_build_query( ['download' => '1', 'device' => 'apple-mobileconfig'] ), + ], 'mobileconfig-mac-new', $basePath, ); +} + +\header( 'Content-Type: text/plain', true, 405 ); +exit( "405 Method Not Allowed\r\n" ); diff --git a/www/profiles/new/_download.php b/www/profiles/new/_download.php deleted file mode 100644 index e0d8d0a..0000000 --- a/www/profiles/new/_download.php +++ /dev/null @@ -1,57 +0,0 @@ - - * Copyright: 2020-2022, Paul Dekkers, SURF - * SPDX-License-Identifier: BSD-3-Clause - */ - -if ( !isset( $downloadKind ) || !isset( $href ) || !isset( $basePath ) ) { - \header( 'Content-Type: text/plain', true, 400 ); - exit( "400 Bad Request\r\n\r\nInvalid request\r\n" ); -} -\assert( \array_key_exists( 'REQUEST_METHOD', $_SERVER ) ); - -$app = new letswifi\LetsWifiApp(); -$app->registerExceptionHandler(); -$realm = $app->getRealm(); -// just trigger a login -$app->getUserFromBrowserSession( $realm ); - -// Create a short-lived cookie to allow the user ONE download without using POST -// If the download would fail, the user is still presented with a download button -// on this page, which uses a more reliable POST. -// If the meta_redirect would go through too late (after cookie expiry), -// the page being redirected to will also contain an appropriate download button. -\setcookie( "${downloadKind}-download-token", (string)\time(), [ - 'expires' => 0, // session cookie - 'httponly' => true, // not available in JavaScript - 'secure' => false, // we don't care, this is not for security, and this helps with local devving - 'path' => '/', // make it available to /profiles/new as well; relative path's don't work here so use "/" for now - 'samesite' => 'Strict', -] ); -if ( isset( $passphrase ) ) { - \setcookie( "${downloadKind}-download-passphrase", $passphrase, [ - 'expires' => 0, // session cookie - 'httponly' => true, // not available in JavaScript - 'secure' => false, // we don't care, this is not for security, and this helps with local devving - 'path' => '/', // make it available to /profiles/new as well; relative path's don't work here so use "/" for now - 'samesite' => 'Strict', - ] ); -} - -switch ( $_SERVER['REQUEST_METHOD'] ) { - case 'GET': return $app->render( - [ - 'href' => $href, - 'passphrase' => ( $passphrase ?? null ) ?: null, - 'action' => "${basePath}/profiles/new/", - 'device' => $downloadKind, - 'meta_redirect' => "${basePath}/profiles/new/?" . \http_build_query( ['download' => '1', 'device' => $downloadKind] ), - ], 'profile-download', $basePath, ); -} - -\header( 'Content-Type: text/plain', true, 405 ); -exit( "405 Method Not Allowed\r\n" ); diff --git a/www/profiles/new/index.php b/www/profiles/new/index.php index 22066d8..a9fcffb 100644 --- a/www/profiles/new/index.php +++ b/www/profiles/new/index.php @@ -17,7 +17,7 @@ $realm = $app->getRealm(); $user = $app->getUserFromBrowserSession( $realm ); -// Workaround for MacOS/ChromeOS flow; we want to provide the download through a meta refresh, +// Workaround for MacOS flow; we want to provide the download through a meta refresh, // but the refresh should only work once. // Here we check a session, check if it can provide a download and then destroy the cookie // so that the next request would point the user to the download page instead of the @@ -40,40 +40,29 @@ // It also does not protect against scripted downloads; // the cookie is easily guessable, but why would a script do that? // It can just POST. +if ( isset( $_COOKIE['mobileconfig-download-token'] ) ) { + \setcookie( 'mobileconfig-download-token', 'delete', 100000 ); +} if ( 'GET' === $_SERVER['REQUEST_METHOD'] && isset( $_GET['download'] ) ) { - foreach ( ['apple-mobileconfig', 'google-onc', 'pkcs12'] as $kind ) { - // Ensure this request can only be served one time - // Does not affect the current $_COOKIE variable - \setcookie("${kind}-download-token", '', [ - 'expires' => 0, - 'httponly' => true, - 'secure' => false, - 'path' => '/', - 'samesite' => 'Strict', - ]); - - if ( $_GET['device'] === $kind && isset( $_COOKIE["${kind}-download-token"] ) ) { - $cookieTime = (int)$_COOKIE["${kind}-download-token"]; - $earliestOkTime = \time() - 60; // allow cookies up to 60 seconds old - if ( \time() >= $cookieTime && $cookieTime >= $earliestOkTime ) { - $overrideMethod = 'POST'; - $overrideDevice = $kind; - } - if ( isset( $_COOKIE["${kind}-download-passphrase"] ) ) { - $overridePassphrase = $_COOKIE["${kind}-download-passphrase"] ?: null; - } + if ( isset( $_COOKIE['mobileconfig-download-token'] ) ) { + $cookieTime = (int)$_COOKIE['mobileconfig-download-token']; + $earliestOkTime = \time() - 60; // allow cookies up to 60 seconds old + if ( \time() >= $cookieTime && $cookieTime >= $earliestOkTime ) { + $fakeMethod = 'POST'; + //$fakeDevice = (string)$_GET['device']; + $fakeDevice = 'apple-mobileconfig'; } } // If we're not willing to fake a POST, // also remove the GET parameters that attempted this from the URL - if ( !isset( $overrideMethod ) && \array_key_exists( 'REQUEST_URI', $_SERVER ) ) { + if ( !isset( $fakeMethod ) && \array_key_exists( 'REQUEST_URI', $_SERVER ) ) { \header( 'Location: ' . \strstr( $_SERVER['REQUEST_URI'], '?', true ) ); exit; } } -switch ( $overrideMethod ?? $_SERVER['REQUEST_METHOD'] ) { +switch ( $fakeMethod ?? $_SERVER['REQUEST_METHOD'] ) { case 'GET': return $app->render( [ 'href' => "${basePath}/profiles/new/", @@ -84,9 +73,6 @@ 'eap-config' => [ 'name' => 'eap-config', ], - 'google-onc' => [ - 'name' => 'ChromeOS', - ], 'pkcs12' => [ 'name' => 'PKCS12', ], @@ -94,19 +80,12 @@ 'app' => [ 'url' => "${basePath}/app/", ], - ], 'profile-advanced', $basePath, ); + ], 'profiles-new', $basePath, ); case 'POST': - $passphrase = $overridePassphrase ?? $_POST['passphrase'] ?: null; - if ( \is_array( $passphrase ) ) { - \header( 'Content-Type: text/plain', true, 400 ); - exit( "400 Bad Request\r\n\r\nInvalid passphrase\r\n" ); - } - switch ( $device = $overrideDevice ?? $_POST['device'] ?? '' ) { - case 'apple-mobileconfig': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\MobileConfigGenerator::class, $user, $passphrase ); break; - case 'eap-config': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\EapConfigGenerator::class, $user, $passphrase ); break; - case 'pkcs12': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\PKCS12Generator::class, $user, $passphrase ); break; - case 'google-onc': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\ONCGenerator::class, $user, $passphrase ); break; - + switch ( $device = $fakeDevice ?? $_POST['device'] ?? '' ) { + case 'apple-mobileconfig': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\MobileConfigGenerator::class, $user ); break; + case 'eap-config': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\EapConfigGenerator::class, $user ); break; + case 'pkcs12': $generator = $realm->getConfigGenerator( \letswifi\profile\generator\PKCS12Generator::class, $user ); break; default: \header( 'Content-Type: text/plain', true, 400 ); $deviceStr = \is_string( $device ) diff --git a/www/profiles/onc/index.php b/www/profiles/onc/index.php deleted file mode 100644 index 006fa2d..0000000 --- a/www/profiles/onc/index.php +++ /dev/null @@ -1,19 +0,0 @@ - - * Copyright: 2020-2022, Paul Dekkers, SURF - * SPDX-License-Identifier: BSD-3-Clause - */ - -require \implode(\DIRECTORY_SEPARATOR, [\dirname(__DIR__, 3), 'src', '_autoload.php']); -$basePath = '../..'; -\assert( \array_key_exists( 'REQUEST_METHOD', $_SERVER ) ); - -$downloadKind = 'google-onc'; -$href = "${basePath}/profiles/onc/"; -$passphrase = $_COOKIE["${downloadKind}-download-passphrase"] ?: \substr( '000' . \random_int( 0, 9999 ), -4 ); - -require \implode(\DIRECTORY_SEPARATOR, [\dirname(__DIR__), 'new', '_download.php']);