├── phpspec.yml.dist ├── src ├── Crypto │ ├── Exception │ │ └── UnsupportedEncryptionMethod.php │ ├── RawBytes.php │ ├── KeyingMaterial.php │ ├── Crypt.php │ ├── KeyGenerator.php │ ├── CipherText.php │ ├── Nonce.php │ ├── SharedSecret.php │ ├── AuthenticationTag.php │ ├── PseudoRandomKey.php │ ├── ContentEncryptionKey.php │ ├── Salt.php │ ├── Info.php │ ├── OpenSSLCrypt.php │ ├── SpomkyLabsCrypt.php │ ├── AggregateCrypt.php │ ├── ExtCryptoCrypt.php │ ├── AuthenticationSecret.php │ ├── PrivateKey.php │ ├── Cipher.php │ ├── BinaryString.php │ ├── KeyPair.php │ ├── Cryptograph.php │ ├── HKDF.php │ └── PublicKey.php ├── Exception │ └── UnsupportedPushService.php ├── PushService.php ├── PushSubscription.php ├── PushClient.php ├── PushNotification.php ├── Endpoint.php ├── PushMessage.php ├── AggregatePushService.php ├── PushServiceRegistry.php ├── Subscription.php ├── MozillaPushService.php ├── Notification.php ├── Client.php ├── Message.php └── GooglePushService.php ├── .gitignore ├── spec ├── Crypto │ ├── KeyGeneratorSpec.php │ ├── SaltSpec.php │ ├── HKDFSpec.php │ ├── CipherTextSpec.php │ ├── KeyPairSpec.php │ ├── NonceSpec.php │ ├── AuthenticationTagSpec.php │ ├── PseudoRandomKeySpec.php │ ├── ContentEncryptionKeySpec.php │ ├── InfoSpec.php │ ├── CipherSpec.php │ ├── SharedSecretSpec.php │ ├── BinaryStringSpec.php │ ├── AggregateCryptSpec.php │ ├── SpomkyLabsCryptSpec.php │ ├── AuthenticationSecretSpec.php │ ├── OpenSSLCryptSpec.php │ ├── PrivateKeySpec.php │ ├── ExtCryptoCryptSpec.php │ ├── CryptographSpec.php │ └── PublicKeySpec.php ├── ClientSpec.php ├── EndpointSpec.php ├── SubscriptionSpec.php ├── PushServiceRegistrySpec.php ├── MozillaPushServiceSpec.php ├── GooglePushServiceSpec.php ├── AggregatePushServiceSpec.php ├── NotificationSpec.php └── MessageSpec.php ├── .travis.yml ├── LICENSE ├── composer.json ├── README.md └── composer.lock /phpspec.yml.dist: -------------------------------------------------------------------------------- 1 | suites: 2 | cmnty_push: 3 | namespace: Cmnty\Push 4 | psr4_prefix: Cmnty\Push 5 | -------------------------------------------------------------------------------- /src/Crypto/Exception/UnsupportedEncryptionMethod.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(KeyGenerator::class); 15 | } 16 | 17 | function it_should_generate_key_pairs() 18 | { 19 | $this->generateKeyPair()->shouldReturnAnInstanceOf(KeyPair::class); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crypto/Crypt.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($service); 15 | } 16 | 17 | function it_is_initializable() 18 | { 19 | $this->shouldHaveType(Client::class); 20 | } 21 | 22 | function it_should_implement_push_client() 23 | { 24 | $this->shouldImplement(PushClient::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Crypto/KeyGenerator.php: -------------------------------------------------------------------------------- 1 | generator256(); 17 | 18 | $eccPrivateKey = $generator->createPrivateKey(); 19 | $privateKey = PrivateKey::createFromEccKey($eccPrivateKey); 20 | 21 | return new KeyPair($privateKey, $privateKey->getPublicKey()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/PushService.php: -------------------------------------------------------------------------------- 1 | binaryString = $binaryString; 20 | } 21 | 22 | /** 23 | * Get raw bytes. 24 | * 25 | * @return string 26 | */ 27 | public function getRawBytes() : string 28 | { 29 | return $this->binaryString->getRawBytes(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/PushSubscription.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(Salt::class); 14 | } 15 | 16 | function it_should_implement_raw_bytes() 17 | { 18 | $this->shouldImplement(RawBytes::class); 19 | } 20 | 21 | function it_should_contain_the_raw_bytes() 22 | { 23 | $this->getRawBytes()->shouldBeString(); 24 | } 25 | 26 | function it_should_contain_a_base64url_encoded_version_of_the_raw_bytes() 27 | { 28 | $this->getBase64UrlEncodedString()->shouldBeString(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | 7 | matrix: 8 | include: 9 | - 10 | php: 7 11 | env: dependencies=lowest 12 | - 13 | php: 7 14 | env: dependencies=highest 15 | 16 | cache: 17 | directories: 18 | - $HOME/.composer/cache 19 | 20 | install: 21 | - pecl install crypto-0.3.0 22 | 23 | before_script: 24 | - travis_retry composer self-update -q 25 | - if [ -z "$dependencies" ]; then travis_retry composer install; fi; 26 | - if [ "$dependencies" = "lowest" ]; then travis_retry composer update --prefer-lowest --prefer-stable -n; fi; 27 | - if [ "$dependencies" = "highest" ]; then travis_retry composer update -n; fi; 28 | 29 | script: 30 | - vendor/bin/phpspec run --format=pretty 31 | -------------------------------------------------------------------------------- /spec/Crypto/HKDFSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(HKDF::class); 16 | } 17 | 18 | function it_should_be_callable(Salt $salt, KeyingMaterial $keyingMaterial) 19 | { 20 | $salt->getRawBytes()->willReturn('raw_bytes'); 21 | $keyingMaterial->getRawKeyMaterial()->willReturn('raw_key_material'); 22 | 23 | $this->__invoke($salt, $keyingMaterial, 'Content-Encoding', 32)->shouldReturnAnInstanceOf(BinaryString::class); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /spec/Crypto/CipherTextSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType(CipherText::class); 22 | } 23 | 24 | function it_should_implement_raw_bytes() 25 | { 26 | $this->shouldImplement(RawBytes::class); 27 | } 28 | 29 | function it_should_contain_the_raw_bytes() 30 | { 31 | $this->getRawBytes()->shouldBeString(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/Crypto/KeyPairSpec.php: -------------------------------------------------------------------------------- 1 | getPublicKey()->willReturn($publicKey); 15 | 16 | $this->beConstructedWith($privateKey, $publicKey); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType(KeyPair::class); 22 | } 23 | 24 | function it_should_contain_a_private_key() 25 | { 26 | $this->getPrivateKey()->shouldReturnAnInstanceOf(PrivateKey::class); 27 | } 28 | 29 | function it_should_contain_a_public_key() 30 | { 31 | $this->getPublicKey()->shouldReturnAnInstanceOf(PublicKey::class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/EndpointSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith(self::URL); 15 | } 16 | 17 | function it_is_initializable() 18 | { 19 | $this->shouldHaveType(Endpoint::class); 20 | } 21 | 22 | function it_should_contain_the_full_url() 23 | { 24 | $this->getUrl()->shouldReturn(self::URL); 25 | } 26 | 27 | function it_should_be_able_to_extract_the_host() 28 | { 29 | $this->getHost()->shouldReturn('example.com'); 30 | } 31 | 32 | function it_should_be_able_to_extract_the_registration_id() 33 | { 34 | $this->getRegistrationId()->shouldReturn('dbDqU8xX10w:APA91b...'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Crypto/Nonce.php: -------------------------------------------------------------------------------- 1 | getLength() != 12) { 24 | throw new InvalidArgumentException('Nonce could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawBytes() : string 36 | { 37 | return $this->binaryString->getRawBytes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/PushClient.php: -------------------------------------------------------------------------------- 1 | getLength() != 32) { 24 | throw new InvalidArgumentException('SharedSecret could not be created: incorrect length.'); 25 | } 26 | 27 | $this->sharedSecret = $sharedSecret; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawKeyMaterial() : string 36 | { 37 | return $this->sharedSecret->getRawBytes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Crypto/AuthenticationTag.php: -------------------------------------------------------------------------------- 1 | getLength() != 16) { 24 | throw new InvalidArgumentException('AuthenticationTag could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawBytes() : string 36 | { 37 | return $this->binaryString->getRawBytes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Crypto/PseudoRandomKey.php: -------------------------------------------------------------------------------- 1 | getLength() != 32) { 24 | throw new InvalidArgumentException('PseudoRandomKey could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawKeyMaterial() : string 36 | { 37 | return $this->binaryString->getRawBytes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Crypto/ContentEncryptionKey.php: -------------------------------------------------------------------------------- 1 | getLength() != 16) { 24 | throw new InvalidArgumentException('ContentEncryptionKey could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawKeyMaterial() : string 36 | { 37 | return $this->binaryString->getRawBytes(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Endpoint.php: -------------------------------------------------------------------------------- 1 | url = $url; 20 | } 21 | 22 | /** 23 | * Get the url. 24 | * 25 | * @return string 26 | */ 27 | public function getUrl() : string 28 | { 29 | return $this->url; 30 | } 31 | 32 | /** 33 | * Get the host. 34 | * 35 | * @return string 36 | */ 37 | public function getHost() : string 38 | { 39 | return parse_url($this->url, PHP_URL_HOST); 40 | } 41 | 42 | /** 43 | * Get the keys. 44 | * 45 | * @return string 46 | */ 47 | public function getRegistrationId() : string 48 | { 49 | preg_match('{^(\S+)(?:/)(?P[^/]+)}', $this->url, $matches); 50 | 51 | return $matches['registrationId']; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 CMNTY Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/Crypto/NonceSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | $binaryString->getLength()->willReturn(12); 18 | } 19 | 20 | function it_is_initializable() 21 | { 22 | $this->shouldHaveType(Nonce::class); 23 | } 24 | 25 | function it_should_implement_raw_bytes() 26 | { 27 | $this->shouldImplement(RawBytes::class); 28 | } 29 | 30 | function it_should_contain_the_raw_bytes() 31 | { 32 | $this->getRawBytes()->shouldBeString(); 33 | } 34 | 35 | function it_should_throw_an_exception_if_the_nonce_has_an_invalid_length(BinaryString $binaryString) 36 | { 37 | $binaryString->getLength()->willReturn(11); 38 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/Crypto/AuthenticationTagSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | $binaryString->getLength()->willReturn(16); 18 | } 19 | 20 | function it_is_initializable() 21 | { 22 | $this->shouldHaveType(AuthenticationTag::class); 23 | } 24 | 25 | function it_should_implement_raw_bytes() 26 | { 27 | $this->shouldImplement(RawBytes::class); 28 | } 29 | 30 | function it_should_contain_the_raw_bytes() 31 | { 32 | $this->getRawBytes()->shouldBeString(); 33 | } 34 | 35 | function it_should_throw_an_exception_if_the_tag_has_an_invalid_length(BinaryString $binaryString) 36 | { 37 | $binaryString->getLength()->willReturn(15); 38 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/PushMessage.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | $binaryString->getLength()->willReturn(32); 18 | } 19 | 20 | function it_is_initializable() 21 | { 22 | $this->shouldHaveType(PseudoRandomKey::class); 23 | } 24 | 25 | function it_should_implement_keying_material() 26 | { 27 | $this->shouldImplement(KeyingMaterial::class); 28 | } 29 | 30 | function it_should_contain_the_raw_key_material() 31 | { 32 | $this->getRawKeyMaterial()->shouldBeString(); 33 | } 34 | 35 | function it_should_throw_an_exception_if_the_pseudo_random_key_has_an_invalid_length(BinaryString $binaryString) 36 | { 37 | $binaryString->getLength()->willReturn(31); 38 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Crypto/Salt.php: -------------------------------------------------------------------------------- 1 | getLength() != 16) { 24 | throw new InvalidArgumentException('Salt could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Get raw bytes. 32 | * 33 | * @return string 34 | */ 35 | public function getRawBytes() : string 36 | { 37 | return $this->binaryString->getRawBytes(); 38 | } 39 | 40 | /** 41 | * Get base64url encoded string. 42 | * 43 | * @return string 44 | */ 45 | public function getBase64UrlEncodedString() : string 46 | { 47 | return $this->binaryString->getBase64UrlEncodedString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/SubscriptionSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($endpoint, $key, $secret); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Subscription::class); 22 | } 23 | 24 | function it_should_implement_push_subscription() 25 | { 26 | $this->shouldImplement(PushSubscription::class); 27 | } 28 | 29 | function it_should_contain_an_endpoint(Endpoint $endpoint) 30 | { 31 | $this->getEndpoint()->shouldReturn($endpoint); 32 | } 33 | 34 | function it_should_contain_a_public_key(PublicKey $key) 35 | { 36 | $this->getPublicKey()->shouldReturn($key); 37 | } 38 | 39 | function it_should_contain_an_authentication_secret(AuthenticationSecret $secret) 40 | { 41 | $this->getAuthenticationSecret()->shouldReturn($secret); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /spec/Crypto/ContentEncryptionKeySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | $binaryString->getLength()->willReturn(16); 18 | } 19 | 20 | function it_is_initializable() 21 | { 22 | $this->shouldHaveType(ContentEncryptionKey::class); 23 | } 24 | 25 | function it_should_implement_keying_material() 26 | { 27 | $this->shouldImplement(KeyingMaterial::class); 28 | } 29 | 30 | function it_should_contain_the_raw_key_material() 31 | { 32 | $this->getRawKeyMaterial()->shouldBeString(); 33 | } 34 | 35 | function it_should_throw_an_exception_if_the_content_encryption_key_has_an_invalid_length(BinaryString $binaryString) 36 | { 37 | $binaryString->getLength()->willReturn(15); 38 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/Crypto/InfoSpec.php: -------------------------------------------------------------------------------- 1 | getRawKeyMaterial()->willReturn('raw_key_material'); 14 | $recipientPublicKey->getLength()->willReturn(65); 15 | $senderPublicKey->getRawKeyMaterial()->willReturn('raw_key_material'); 16 | $senderPublicKey->getLength()->willReturn(65); 17 | 18 | $this->beConstructedWith($recipientPublicKey, $senderPublicKey); 19 | } 20 | 21 | function it_is_initializable() 22 | { 23 | $this->shouldHaveType(Info::class); 24 | } 25 | 26 | function it_should_generate_content_encoding_for_auth() 27 | { 28 | $this->getContentEncoding('auth')->shouldBeString(); 29 | } 30 | 31 | function it_should_generate_content_encoding_for_content_encryption_key() 32 | { 33 | $this->getContentEncoding('aesgcm')->shouldBeString(); 34 | } 35 | 36 | function it_should_generate_content_encoding_for_nonce() 37 | { 38 | $this->getContentEncoding('nonce')->shouldBeString(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/Crypto/CipherSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($cipherText, $authenticationTag, $salt, $publicKey); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType(Cipher::class); 22 | } 23 | 24 | function it_should_contain_the_cipher_text() 25 | { 26 | $this->getCipherText()->shouldReturnAnInstanceOf(CipherText::class); 27 | } 28 | 29 | function it_should_contain_the_authentication_tag() 30 | { 31 | $this->getAuthenticationTag()->shouldReturnAnInstanceOf(AuthenticationTag::class); 32 | } 33 | 34 | function it_should_contain_the_salt() 35 | { 36 | $this->getSalt()->shouldReturnAnInstanceOf(Salt::class); 37 | } 38 | 39 | function it_should_contain_the_public_key() 40 | { 41 | $this->getPublicKey()->shouldReturnAnInstanceOf(PublicKey::class); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmnty/push", 3 | "description": "Web Push library for PHP", 4 | "keywords": ["Notifications", "Push Notifications", "Push Messages", "Push", "Web Push", "Web Push API", "Push API"], 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Johan de Ruijter", 10 | "email": "johan@cmnty.com" 11 | } 12 | ], 13 | "require": { 14 | "php": "~7.0", 15 | "guzzlehttp/guzzle": "~6.2.1", 16 | "mdanter/ecc": "~0.4.0", 17 | "spomky-labs/base64url": "~1.0" 18 | }, 19 | "require-dev": { 20 | "ext-crypto": "*", 21 | "lib-openssl": "*", 22 | "spomky-labs/php-aes-gcm": "~1.0", 23 | "phpspec/phpspec": "~3.2", 24 | "phpspec/prophecy": "~1.6" 25 | }, 26 | "suggest": { 27 | "ext-crypto": "Use ext-crypto to encrypt the notifications", 28 | "lib-openssl": "Use lib-openssl to encrypt the notifications", 29 | "spomky-labs/php-aes-gcm": "Use native php to encrypt the notifications. This is significantly slower than the alternatives." 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Cmnty\\Push\\": "src" 34 | } 35 | }, 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "1.0-dev" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spec/Crypto/SharedSecretSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString, $recipientPublicKey, $senderPublicKey); 16 | 17 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 18 | $binaryString->getLength()->willReturn(32); 19 | } 20 | 21 | function it_is_initializable() 22 | { 23 | $this->shouldHaveType(SharedSecret::class); 24 | } 25 | 26 | function it_should_implement_keying_material() 27 | { 28 | $this->shouldImplement(KeyingMaterial::class); 29 | } 30 | 31 | function it_should_contain_the_raw_key_material() 32 | { 33 | $this->getRawKeyMaterial()->shouldBeString(); 34 | } 35 | 36 | function it_should_throw_an_exception_if_the_shared_secret_has_an_invalid_length(BinaryString $binaryString) 37 | { 38 | $binaryString->getLength()->willReturn(31); 39 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /spec/PushServiceRegistrySpec.php: -------------------------------------------------------------------------------- 1 | addPushService($service); 15 | $service->supportsHost('example.com')->willReturn(true); 16 | $service->supportsHost('example.org')->willReturn(false); 17 | } 18 | 19 | function it_is_initializable() 20 | { 21 | $this->shouldHaveType(PushServiceRegistry::class); 22 | } 23 | 24 | function it_should_confirm_a_push_service_is_supported() 25 | { 26 | $this->hasPushService('example.com')->shouldReturn(true); 27 | } 28 | 29 | function it_should_confirm_a_push_service_is_not_supported() 30 | { 31 | $this->hasPushService('example.org')->shouldReturn(false); 32 | } 33 | 34 | function it_should_store_push_services(PushService $service) 35 | { 36 | $this->getPushService('example.com')->shouldReturn($service); 37 | } 38 | 39 | function it_should_throw_excepton_when_host_is_not_supported() 40 | { 41 | $this->shouldThrow(UnsupportedPushService::class)->during('getPushService', ['example.org']); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Crypto/Info.php: -------------------------------------------------------------------------------- 1 | recipientPublicKey = $recipientPublicKey; 26 | $this->senderPublicKey = $senderPublicKey; 27 | } 28 | 29 | public function getContentEncoding(string $type) : string 30 | { 31 | if ($type === 'auth') { 32 | return 33 | 'Content-Encoding: ' 34 | .$type 35 | .chr(0) 36 | ; 37 | } 38 | 39 | return 40 | 'Content-Encoding: ' 41 | .$type 42 | .chr(0) 43 | .'P-256' 44 | .chr(0) 45 | .pack('n', $this->recipientPublicKey->getLength()) 46 | .$this->recipientPublicKey->getRawKeyMaterial() 47 | .pack('n', $this->senderPublicKey->getLength()) 48 | .$this->senderPublicKey->getRawKeyMaterial() 49 | ; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spec/Crypto/BinaryStringSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('raw_bytes'); 14 | } 15 | 16 | function it_is_initializable() 17 | { 18 | $this->shouldHaveType(BinaryString::class); 19 | } 20 | 21 | function it_should_implement_raw_bytes() 22 | { 23 | $this->shouldImplement(RawBytes::class); 24 | } 25 | 26 | function it_should_contain_the_raw_bytes() 27 | { 28 | $this->getRawBytes()->shouldBeString(); 29 | } 30 | 31 | function it_should_contain_a_base64url_encoded_version_of_the_raw_bytes() 32 | { 33 | $this->getBase64UrlEncodedString()->shouldBeString(); 34 | } 35 | 36 | function it_should_know_its_length() 37 | { 38 | $this->getLength()->shouldBeInt(); 39 | } 40 | 41 | function it_should_be_able_to_concatenate_itself_with_another_binary_string(BinaryString $binaryString) 42 | { 43 | $binaryString->getRawBytes()->willReturn('more_raw_bytes'); 44 | 45 | $this->concat($binaryString)->shouldReturnAnInstanceOf(BinaryString::class); 46 | } 47 | 48 | function it_should_be_able_to_slice_itself_to_a_certain_length() 49 | { 50 | $this->slice(16)->shouldReturnAnInstanceOf(BinaryString::class); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Crypto/OpenSSLCrypt.php: -------------------------------------------------------------------------------- 1 | getRawKeyMaterial(), OPENSSL_RAW_DATA, $nonce->getRawBytes(), $tag); 35 | 36 | $cipherText = new CipherText(new BinaryString($encryptedText)); 37 | $tag = new AuthenticationTag(new BinaryString($tag)); 38 | 39 | return new Cipher($cipherText, $tag, $salt, $senderPublicKey); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Crypto/SpomkyLabsCrypt.php: -------------------------------------------------------------------------------- 1 | getRawKeyMaterial(), $nonce->getRawBytes(), $plainText, ''); 36 | 37 | $cipherText = new CipherText(new BinaryString($encryptedText)); 38 | $tag = new AuthenticationTag(new BinaryString($tag)); 39 | 40 | return new Cipher($cipherText, $tag, $salt, $senderPublicKey); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/AggregatePushService.php: -------------------------------------------------------------------------------- 1 | pushServiceRegistry = $registry; 23 | } 24 | 25 | /** 26 | * Check weather this push service supports a certain host. 27 | * 28 | * @param string $host 29 | * 30 | * @return bool 31 | */ 32 | public function supportsHost(string $host) : bool 33 | { 34 | return $this->pushServiceRegistry->hasPushService($host); 35 | } 36 | 37 | /** 38 | * Create a push request. 39 | * 40 | * @param PushMessage $message 41 | * 42 | * @return RequestInterface 43 | * 44 | * @throws UnsupportedPushService When no push service that supports the given push message is found. 45 | */ 46 | public function createRequest(PushMessage $message) : RequestInterface 47 | { 48 | $host = $message->getPushSubscription()->getEndpoint()->getHost(); 49 | $pushService = $this->pushServiceRegistry->getPushService($host); 50 | 51 | return $pushService->createRequest($message); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/Crypto/AggregateCryptSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(AggregateCrypt::class); 20 | } 21 | 22 | function it_should_implement_crypt() 23 | { 24 | $this->shouldImplement(Crypt::class); 25 | } 26 | 27 | function it_should_encrypt_a_string(ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $publicKey) 28 | { 29 | $contentEncryptionKey->getRawKeyMaterial()->willReturn(base64_decode('1478s12rIuzznquRpokhdw')); 30 | $nonce->getRawBytes()->willReturn(base64_decode('QN6zugACy0bYogh8')); 31 | $salt->getRawBytes()->willReturn(base64_decode('Cv0fRYvjK3xSDOxPkRXtlg')); 32 | $publicKey->getRawKeyMaterial()->willReturn(base64_decode('BP4SlAJQhcxGYerW3gZusOx5osiRvkn0Q79CTiXbOPP9QH6l//17D3MkAdhx6GEytbLVQRtVO6xeb8XuaP7qzeA')); 33 | 34 | $this->encrypt( 35 | Argument::type('string'), 36 | $contentEncryptionKey, 37 | $nonce, 38 | $salt, 39 | $publicKey 40 | )->shouldReturnAnInstanceOf(Cipher::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /spec/Crypto/SpomkyLabsCryptSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(SpomkyLabsCrypt::class); 20 | } 21 | 22 | function it_should_implement_crypt() 23 | { 24 | $this->shouldImplement(Crypt::class); 25 | } 26 | 27 | function it_should_encrypt_a_string(ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $publicKey) 28 | { 29 | $contentEncryptionKey->getRawKeyMaterial()->willReturn(base64_decode('1478s12rIuzznquRpokhdw')); 30 | $nonce->getRawBytes()->willReturn(base64_decode('QN6zugACy0bYogh8')); 31 | $salt->getRawBytes()->willReturn(base64_decode('Cv0fRYvjK3xSDOxPkRXtlg')); 32 | $publicKey->getRawKeyMaterial()->willReturn(base64_decode('BP4SlAJQhcxGYerW3gZusOx5osiRvkn0Q79CTiXbOPP9QH6l//17D3MkAdhx6GEytbLVQRtVO6xeb8XuaP7qzeA')); 33 | 34 | $this->encrypt( 35 | Argument::type('string'), 36 | $contentEncryptionKey, 37 | $nonce, 38 | $salt, 39 | $publicKey 40 | )->shouldReturnAnInstanceOf(Cipher::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/PushServiceRegistry.php: -------------------------------------------------------------------------------- 1 | pushServices[] = $pushService; 22 | } 23 | 24 | /** 25 | * Check whether a push service exists in the registry. 26 | * 27 | * @param string $host 28 | * 29 | * @return bool 30 | */ 31 | public function hasPushService($host) : bool 32 | { 33 | foreach ($this->pushServices as $pushService) { 34 | if ($pushService->supportsHost($host)) { 35 | return true; 36 | } 37 | } 38 | 39 | return false; 40 | } 41 | 42 | /** 43 | * Get a push service from the registry. 44 | * 45 | * @param string $host 46 | * 47 | * @return PushService 48 | * 49 | * @throws UnsupportedPushService When no push service that supports the given host is found. 50 | */ 51 | public function getPushService($host) : PushService 52 | { 53 | foreach ($this->pushServices as $pushService) { 54 | if ($pushService->supportsHost($host)) { 55 | return $pushService; 56 | } 57 | } 58 | 59 | throw UnsupportedPushService::forHost($host); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spec/MozillaPushServiceSpec.php: -------------------------------------------------------------------------------- 1 | getPushSubscription()->willReturn($subscription); 18 | 19 | $message->getBody()->willReturn('cipher_text'); 20 | $message->getContentLength()->willReturn(256); 21 | $message->getSalt()->willReturn('salt'); 22 | $message->getCryptoKey()->willReturn('key'); 23 | $message->getTTL()->willReturn(3600); 24 | 25 | $subscription->getEndpoint()->willReturn($endpoint); 26 | 27 | $endpoint->getUrl()->willReturn('mozilla.push.services'); 28 | } 29 | 30 | function it_is_initializable() 31 | { 32 | $this->shouldHaveType(MozillaPushService::class); 33 | } 34 | 35 | function it_should_implement_push_service() 36 | { 37 | $this->shouldImplement(PushService::class); 38 | } 39 | 40 | function it_should_confirm_mozilla_is_supported() 41 | { 42 | $this->supportsHost('updates.push.services.mozilla.com')->shouldReturn(true); 43 | } 44 | 45 | function it_should_create_a_request_from_a_message(PushMessage $message) 46 | { 47 | $this->createRequest($message)->shouldReturnAnInstanceOf(RequestInterface::class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/GooglePushServiceSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('API_Key'); 18 | 19 | $message->getPushSubscription()->willReturn($subscription); 20 | 21 | $message->getBody()->willReturn('cipher_text'); 22 | $message->getContentLength()->willReturn(256); 23 | $message->getSalt()->willReturn('salt'); 24 | $message->getCryptoKey()->willReturn('key'); 25 | $message->getTTL()->willReturn(3600); 26 | 27 | $subscription->getEndpoint()->willReturn($endpoint); 28 | 29 | $endpoint->getRegistrationId()->willReturn('registration_id'); 30 | } 31 | 32 | function it_is_initializable() 33 | { 34 | $this->shouldHaveType(GooglePushService::class); 35 | } 36 | 37 | function it_should_implement_push_service() 38 | { 39 | $this->shouldImplement(PushService::class); 40 | } 41 | 42 | function it_should_confirm_google_is_supported() 43 | { 44 | $this->supportsHost('android.googleapis.com')->shouldReturn(true); 45 | } 46 | 47 | function it_should_create_a_request_from_a_message(PushMessage $message) 48 | { 49 | $this->createRequest($message)->shouldReturnAnInstanceOf(RequestInterface::class); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Subscription.php: -------------------------------------------------------------------------------- 1 | endpoint = $endpoint; 35 | $this->publicKey = $publicKey; 36 | $this->authenticationSecret = $authenticationSecret; 37 | } 38 | 39 | /** 40 | * Get the endpoint. 41 | * 42 | * @return Endpoint 43 | */ 44 | public function getEndpoint() : Endpoint 45 | { 46 | return $this->endpoint; 47 | } 48 | 49 | /** 50 | * Get the public key. 51 | * 52 | * @return PublicKey 53 | */ 54 | public function getPublicKey() : PublicKey 55 | { 56 | return $this->publicKey; 57 | } 58 | 59 | /** 60 | * Get the authentication tag. 61 | * 62 | * @return AuthenticationSecret 63 | */ 64 | public function getAuthenticationSecret() : AuthenticationSecret 65 | { 66 | return $this->authenticationSecret; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Crypto/AggregateCrypt.php: -------------------------------------------------------------------------------- 1 | crypt = new $crypt(); 30 | 31 | return; 32 | } catch (UnsupportedEncryptionMethod $e) {} 33 | } 34 | 35 | throw new UnsupportedEncryptionMethod('No supported encryption library found. Please install one of the following libraries or extensions: ext-crypto, lib-openssl, or spomky-labs/php-aes-gcm.'); 36 | } 37 | 38 | /** 39 | * Encrypt the plain text sting. 40 | * 41 | * @param string $plainText 42 | * @param ContentEncryptionKey $contentEncryptionKey 43 | * @param Nonce $nonce 44 | * @param Salt $salt 45 | * @param PublicKey $senderPublicKey 46 | * 47 | * @return Cipher 48 | */ 49 | public function encrypt(string $plainText, ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $senderPublicKey) : Cipher 50 | { 51 | return $this->crypt->encrypt($plainText, $contentEncryptionKey, $nonce, $salt, $senderPublicKey); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /spec/AggregatePushServiceSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($registry); 19 | 20 | $registry->hasPushService('example.com')->willReturn(true); 21 | $registry->getPushService('example.com')->willReturn($service); 22 | 23 | $service->createRequest($message)->willReturn($request); 24 | 25 | $message->getPushSubscription()->willReturn($subscription); 26 | 27 | $subscription->getEndpoint()->willReturn($endpoint); 28 | 29 | $endpoint->getHost()->willReturn('example.com'); 30 | } 31 | 32 | function it_is_initializable() 33 | { 34 | $this->shouldHaveType(AggregatePushService::class); 35 | } 36 | 37 | function it_should_implement_push_service() 38 | { 39 | $this->shouldImplement(PushService::class); 40 | } 41 | 42 | function it_should_confirm_a_host_is_supported() 43 | { 44 | $this->supportsHost('example.com')->shouldReturn(true); 45 | } 46 | 47 | function it_should_create_a_request_from_a_message(PushMessage $message, RequestInterface $request) 48 | { 49 | $this->createRequest($message)->shouldReturn($request); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /spec/Crypto/AuthenticationSecretSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 15 | 16 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 17 | $binaryString->getBase64UrlEncodedString()->willReturn('base64url'); 18 | $binaryString->getLength()->willReturn(16); 19 | } 20 | 21 | function it_is_initializable() 22 | { 23 | $this->shouldHaveType(AuthenticationSecret::class); 24 | } 25 | 26 | function it_should_implement_raw_bytes() 27 | { 28 | $this->shouldImplement(RawBytes::class); 29 | } 30 | 31 | function it_should_contain_the_raw_bytes() 32 | { 33 | $this->getRawBytes()->shouldBeString(); 34 | } 35 | 36 | function it_should_contain_a_base64url_encoded_version_of_the_raw_bytes() 37 | { 38 | $this->getBase64UrlEncodedString()->shouldBeString(); 39 | } 40 | 41 | function it_should_throw_an_exception_if_the_secret_has_an_invalid_length(BinaryString $binaryString) 42 | { 43 | $binaryString->getLength()->willReturn(15); 44 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 45 | } 46 | 47 | function it_can_be_created_from_a_base64url_encoded_string() 48 | { 49 | $this->beConstructedThrough('createFromBase64UrlEncodedString', [ 50 | 'giIO3ijRFJuoDuH1w4DKYg', 51 | ]); 52 | 53 | $this->shouldHaveType(AuthenticationSecret::class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /spec/Crypto/OpenSSLCryptSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(OpenSSLCrypt::class); 21 | } 22 | 23 | function it_should_implement_crypt() 24 | { 25 | $this->shouldImplement(Crypt::class); 26 | } 27 | 28 | function it_should_encrypt_a_string(ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $publicKey) 29 | { 30 | if (PHP_VERSION_ID < 70100) { 31 | $this->shouldThrow(UnsupportedEncryptionMethod::class)->duringInstantiation(); 32 | 33 | return; 34 | } 35 | 36 | $contentEncryptionKey->getRawKeyMaterial()->willReturn(base64_decode('1478s12rIuzznquRpokhdw')); 37 | $nonce->getRawBytes()->willReturn(base64_decode('QN6zugACy0bYogh8')); 38 | $salt->getRawBytes()->willReturn(base64_decode('Cv0fRYvjK3xSDOxPkRXtlg')); 39 | $publicKey->getRawKeyMaterial()->willReturn(base64_decode('BP4SlAJQhcxGYerW3gZusOx5osiRvkn0Q79CTiXbOPP9QH6l//17D3MkAdhx6GEytbLVQRtVO6xeb8XuaP7qzeA')); 40 | 41 | $this->encrypt( 42 | Argument::type('string'), 43 | $contentEncryptionKey, 44 | $nonce, 45 | $salt, 46 | $publicKey 47 | )->shouldReturnAnInstanceOf(Cipher::class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/Crypto/PrivateKeySpec.php: -------------------------------------------------------------------------------- 1 | generator256(); 19 | $eccPrivateKey = $generator->createPrivateKey(); 20 | 21 | $this->beConstructedThrough('createFromEccKey', [$eccPrivateKey]); 22 | } 23 | 24 | function it_is_initializable() 25 | { 26 | $this->shouldHaveType(PrivateKey::class); 27 | } 28 | 29 | function it_should_contain_an_ecc_private_key() 30 | { 31 | $this->getEccKey()->shouldReturnAnInstanceOf(PrivateKeyInterface::class); 32 | } 33 | 34 | function it_should_be_able_to_generate_a_public_key() 35 | { 36 | $this->getPublicKey()->shouldReturnAnInstanceOf(PublicKey::class); 37 | } 38 | 39 | function it_should_be_able_to_calculate_a_shared_secret(PublicKey $publicKey) 40 | { 41 | $generator = EccFactory::getNistCurves()->generator256(); 42 | $eccPrivateKey = $generator->createPrivateKey(); 43 | 44 | $publicKey->getEccKey()->willReturn($eccPrivateKey->getPublicKey()); 45 | 46 | $this->calculateSharedSecret($publicKey)->shouldReturnAnInstanceOf(SharedSecret::class); 47 | } 48 | 49 | function it_should_have_a_non_public_constructor() 50 | { 51 | $this->beConstructedWith(); 52 | $this->shouldThrow('Throwable')->duringInstantiation(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Crypto/ExtCryptoCrypt.php: -------------------------------------------------------------------------------- 1 | = 70100 && version_compare(phpversion('crypto'), '0.3.0', '<')) { 22 | throw new UnsupportedEncryptionMethod('ext-crypto must be as least version 0.3.0 when using php 7.1 or higher.'); 23 | } 24 | } 25 | 26 | /** 27 | * Encrypt the plain text sting. 28 | * 29 | * @param string $plainText 30 | * @param ContentEncryptionKey $contentEncryptionKey 31 | * @param Nonce $nonce 32 | * @param Salt $salt 33 | * @param PublicKey $senderPublicKey 34 | * 35 | * @return Cipher 36 | */ 37 | public function encrypt(string $plainText, ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $senderPublicKey) : Cipher 38 | { 39 | $cipher = new ExtCryptoCipher('aes-128-gcm'); 40 | $cipherText = new CipherText(new BinaryString($cipher->encrypt($plainText, $contentEncryptionKey->getRawKeyMaterial(), $nonce->getRawBytes()))); 41 | $tag = new AuthenticationTag(new BinaryString($cipher->getTag())); 42 | 43 | return new Cipher($cipherText, $tag, $salt, $senderPublicKey); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Crypto/AuthenticationSecret.php: -------------------------------------------------------------------------------- 1 | getLength() != 16) { 24 | throw new InvalidArgumentException('AuthenticationSecret could not be created: incorrect length.'); 25 | } 26 | 27 | $this->binaryString = $binaryString; 28 | } 29 | 30 | /** 31 | * Create authentication secret. 32 | * 33 | * @param string $base64UrlEncoded 34 | * 35 | * @return self 36 | * 37 | * @throws InvalidArgumentException When the authentication secret is not the correct length. 38 | */ 39 | public static function createFromBase64UrlEncodedString(string $base64UrlEncoded) : self 40 | { 41 | return new self(BinaryString::createFromBase64UrlEncodedString($base64UrlEncoded)); 42 | } 43 | 44 | /** 45 | * Get raw bytes. 46 | * 47 | * @return string 48 | */ 49 | public function getRawBytes() : string 50 | { 51 | return $this->binaryString->getRawBytes(); 52 | } 53 | 54 | /** 55 | * Get base64url encoded string. 56 | * 57 | * @return string 58 | */ 59 | public function getBase64UrlEncodedString() : string 60 | { 61 | return $this->binaryString->getBase64UrlEncodedString(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Crypto/PrivateKey.php: -------------------------------------------------------------------------------- 1 | eccKey = $eccKey; 25 | 26 | return $key; 27 | } 28 | 29 | /** 30 | * Get the public key. 31 | * 32 | * @return PublicKey 33 | */ 34 | public function getPublicKey() : PublicKey 35 | { 36 | return PublicKey::createFromEccKey($this->getEccKey()->getPublicKey()); 37 | } 38 | 39 | /** 40 | * Get the private key as a Mdanter Ecc Private Key. 41 | * 42 | * @return PrivateKeyInterface 43 | */ 44 | public function getEccKey() : PrivateKeyInterface 45 | { 46 | return $this->eccKey; 47 | } 48 | 49 | /** 50 | * Calculate a shared secret. 51 | * 52 | * @param PublicKey $publicKey The recipients public key. 53 | * 54 | * @return SharedSecret 55 | */ 56 | public function calculateSharedSecret(PublicKey $publicKey) : SharedSecret 57 | { 58 | $exchange = $this->getEccKey()->createExchange($publicKey->getEccKey()); 59 | $binary = new BinaryString(gmp_export($exchange->calculateSharedKey())); 60 | 61 | return new SharedSecret($binary); 62 | } 63 | 64 | /** 65 | * Prevent the PrivateKey from being instantiated in an invalid state. 66 | */ 67 | private function __construct() {} 68 | } 69 | -------------------------------------------------------------------------------- /spec/Crypto/ExtCryptoCryptSpec.php: -------------------------------------------------------------------------------- 1 | shouldHaveType(ExtCryptoCrypt::class); 21 | } 22 | 23 | function it_should_implement_crypt() 24 | { 25 | $this->shouldImplement(Crypt::class); 26 | } 27 | 28 | function it_should_encrypt_a_string(ContentEncryptionKey $contentEncryptionKey, Nonce $nonce, Salt $salt, PublicKey $publicKey) 29 | { 30 | if (PHP_VERSION_ID >= 70100 && version_compare(phpversion('crypto'), '0.3.0', '<')) { 31 | $this->shouldThrow(UnsupportedEncryptionMethod::class)->duringInstantiation(); 32 | 33 | return; 34 | } 35 | 36 | $contentEncryptionKey->getRawKeyMaterial()->willReturn(base64_decode('1478s12rIuzznquRpokhdw')); 37 | $nonce->getRawBytes()->willReturn(base64_decode('QN6zugACy0bYogh8')); 38 | $salt->getRawBytes()->willReturn(base64_decode('Cv0fRYvjK3xSDOxPkRXtlg')); 39 | $publicKey->getRawKeyMaterial()->willReturn(base64_decode('BP4SlAJQhcxGYerW3gZusOx5osiRvkn0Q79CTiXbOPP9QH6l//17D3MkAdhx6GEytbLVQRtVO6xeb8XuaP7qzeA')); 40 | 41 | $this->encrypt( 42 | Argument::type('string'), 43 | $contentEncryptionKey, 44 | $nonce, 45 | $salt, 46 | $publicKey 47 | )->shouldReturnAnInstanceOf(Cipher::class); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spec/Crypto/CryptographSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($crypt); 24 | 25 | $crypt->encrypt( 26 | Argument::type('string'), 27 | Argument::type(ContentEncryptionKey::class), 28 | Argument::type(Nonce::class), 29 | Argument::type(Salt::class), 30 | Argument::type(PublicKey::class) 31 | )->willReturn($cipher); 32 | } 33 | 34 | function it_is_initializable() 35 | { 36 | $this->shouldHaveType(Cryptograph::class); 37 | } 38 | 39 | function it_should_encrypt_a_push_notification(PushNotification $notification, Subscription $subscription) 40 | { 41 | $notification->json()->willReturn('{"title":"Here be dragons","body":"Winter is coming"}'); 42 | 43 | $key = PublicKey::createFromBase64UrlEncodedString('BEVYVy0G5j5KhryGGTGZNJejRZJRRVa1BLsCFxZQVZBtLFEso1Tkug8Zji3zX7JPoyLzYn7RXMfj3hd6MwLTqsk'); 44 | $secret = AuthenticationSecret::createFromBase64UrlEncodedString('giIO3ijRFJuoDuH1w4DKYg'); 45 | 46 | $subscription->getPublicKey()->willReturn($key); 47 | $subscription->getAuthenticationSecret()->willReturn($secret); 48 | 49 | $this->encrypt($notification, $subscription)->shouldReturnAnInstanceOf(Cipher::class); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Crypto/Cipher.php: -------------------------------------------------------------------------------- 1 | cipherText = $cipherText; 38 | $this->authenticationTag = $authenticationTag; 39 | $this->salt = $salt; 40 | $this->publicKey = $publicKey; 41 | } 42 | 43 | /** 44 | * Get the cypher text. 45 | * 46 | * @return CipherText 47 | */ 48 | public function getCipherText() : CipherText 49 | { 50 | return $this->cipherText; 51 | } 52 | 53 | /** 54 | * Get the authentication tag. 55 | * 56 | * @return AuthenticationTag 57 | */ 58 | public function getAuthenticationTag() : AuthenticationTag 59 | { 60 | return $this->authenticationTag; 61 | } 62 | 63 | /** 64 | * Get the salt used during encryption. 65 | * 66 | * @return Salt 67 | */ 68 | public function getSalt() : Salt 69 | { 70 | return $this->salt; 71 | } 72 | 73 | /** 74 | * Get the public key 75 | * 76 | * @return PublicKey 77 | */ 78 | public function getPublicKey() : PublicKey 79 | { 80 | return $this->publicKey; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /spec/NotificationSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith('title', 'body', 'url', 'icon'); 14 | } 15 | 16 | function it_is_initializable() 17 | { 18 | $this->shouldHaveType(Notification::class); 19 | } 20 | 21 | function it_implements_push_notification_interface() 22 | { 23 | $this->shouldImplement(PushNotification::class); 24 | } 25 | 26 | function it_should_contain_a_title() 27 | { 28 | $this->getTitle()->shouldReturn('title'); 29 | } 30 | 31 | function it_throws_an_error_when_no_title_is_supplied() 32 | { 33 | $this->beConstructedWith(); 34 | $this->shouldThrow('TypeError')->duringInstantiation(); 35 | } 36 | 37 | function it_should_contain_a_body() 38 | { 39 | $this->getBody()->shouldReturn('body'); 40 | } 41 | 42 | function it_throws_an_error_when_no_body_is_supplied() 43 | { 44 | $this->beConstructedWith('title'); 45 | $this->shouldThrow('TypeError')->duringInstantiation(); 46 | } 47 | 48 | function it_can_contain_a_url() 49 | { 50 | $this->getUrl()->shouldReturn('url'); 51 | } 52 | 53 | function it_does_not_have_to_contain_a_url() 54 | { 55 | $this->beConstructedWith('title', 'body'); 56 | $this->getUrl()->shouldReturn(null); 57 | } 58 | 59 | function it_can_contain_an_icon() 60 | { 61 | $this->getIcon()->shouldReturn('icon'); 62 | } 63 | 64 | function it_does_not_have_to_contain_an_icon() 65 | { 66 | $this->beConstructedWith('title', 'body'); 67 | $this->getIcon()->shouldReturn(null); 68 | } 69 | 70 | function it_should_be_able_to_serialize_to_json() 71 | { 72 | $this->json()->shouldReturn('{"title":"title","body":"body","url":"url","icon":"icon"}'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Crypto/BinaryString.php: -------------------------------------------------------------------------------- 1 | rawBytes = $rawBytes; 22 | } 23 | 24 | /** 25 | * Create a binary string from a base64url encoded string. 26 | * 27 | * @param string $base64UrlEncoded 28 | * 29 | * @return self 30 | */ 31 | public static function createFromBase64UrlEncodedString(string $base64UrlEncoded) : self 32 | { 33 | return new self(Base64Url::decode($base64UrlEncoded)); 34 | } 35 | 36 | /** 37 | * Get raw bytes. 38 | * 39 | * @return string 40 | */ 41 | public function getRawBytes() : string 42 | { 43 | return $this->rawBytes; 44 | } 45 | 46 | /** 47 | * Get base64url encoded string. 48 | * 49 | * @return string 50 | */ 51 | public function getBase64UrlEncodedString() : string 52 | { 53 | return Base64Url::encode($this->getRawBytes()); 54 | } 55 | 56 | /** 57 | * Get the length of the binary string. 58 | * 59 | * @return int 60 | */ 61 | public function getLength() : int 62 | { 63 | return strlen($this->getRawBytes()); 64 | } 65 | 66 | /** 67 | * Concatenate the raw bytes with an other binary string. 68 | * 69 | * @param BinaryString $binaryString 70 | * 71 | * @return BinaryString 72 | */ 73 | public function concat(BinaryString $binaryString) : BinaryString 74 | { 75 | return new BinaryString($this->getRawBytes() . $binaryString->getRawBytes()); 76 | } 77 | 78 | /** 79 | * Slice the raw bytes to the given length. 80 | * 81 | * @param int $length 82 | * 83 | * @return BinaryString 84 | */ 85 | public function slice(int $length) : BinaryString 86 | { 87 | return new BinaryString(substr($this->getRawBytes(), 0, $length)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/MozillaPushService.php: -------------------------------------------------------------------------------- 1 | getUrl($message), 36 | $this->getHeaders($message), 37 | $this->getBody($message) 38 | ); 39 | 40 | return $request; 41 | } 42 | 43 | /** 44 | * Get the request url 45 | * 46 | * @param PushMessage $message 47 | * 48 | * @return string 49 | */ 50 | private function getUrl(PushMessage $message) : string 51 | { 52 | return $message->getPushSubscription()->getEndpoint()->getUrl(); 53 | } 54 | 55 | /** 56 | * Get the request headers 57 | * 58 | * @param PushMessage $message 59 | * 60 | * @return string[] 61 | */ 62 | private function getHeaders(PushMessage $message) : array 63 | { 64 | return [ 65 | 'Content-Type' => 'application/json', 66 | 'Content-Length' => $message->getContentLength(), 67 | 'Encryption' => 'keyid=p256dh;salt='.$message->getSalt(), 68 | 'Crypto-Key' => 'keyid=p256dh;dh='.$message->getCryptoKey(), 69 | 'Content-Encoding' => 'aesgcm', 70 | 'TTL' => $message->getTTL(), 71 | ]; 72 | } 73 | 74 | /** 75 | * Get the request body 76 | * 77 | * @param PushMessage $message 78 | * 79 | * @return string 80 | */ 81 | private function getBody(PushMessage $message) : string 82 | { 83 | return $message->getBody(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Notification.php: -------------------------------------------------------------------------------- 1 | title = $title; 38 | $this->body = $body; 39 | $this->url = $url; 40 | $this->icon = $icon; 41 | } 42 | 43 | /** 44 | * Convert the push notification into a json string. 45 | * 46 | * @return string 47 | */ 48 | public function __toString() 49 | { 50 | return $this->json(); 51 | } 52 | 53 | /** 54 | * Convert the push notification into a json string. 55 | * 56 | * @return string 57 | */ 58 | public function json() : string 59 | { 60 | return json_encode([ 61 | 'title' => $this->title, 62 | 'body' => $this->body, 63 | 'url' => $this->url, 64 | 'icon' => $this->icon, 65 | ]); 66 | } 67 | 68 | /** 69 | * Get the title. 70 | * 71 | * @return string 72 | */ 73 | public function getTitle() : string 74 | { 75 | return $this->title; 76 | } 77 | 78 | /** 79 | * Get the body. 80 | * 81 | * @return string 82 | */ 83 | public function getBody() : string 84 | { 85 | return $this->body; 86 | } 87 | 88 | /** 89 | * Get the url. 90 | * 91 | * @return string|null 92 | */ 93 | public function getUrl() 94 | { 95 | return $this->url; 96 | } 97 | 98 | /** 99 | * Get the icon. 100 | * 101 | * @return string|null 102 | */ 103 | public function getIcon() 104 | { 105 | return $this->icon; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Crypto/KeyPair.php: -------------------------------------------------------------------------------- 1 | isValidKeyPair($private, $public)) { 32 | throw new InvalidArgumentException('KeyPair could not be created: private and public key do not match.'); 33 | } 34 | 35 | $this->private = $private; 36 | $this->public = $public; 37 | } 38 | 39 | /** 40 | * Get the private key. 41 | * 42 | * @return PrivateKey 43 | */ 44 | public function getPrivateKey() : PrivateKey 45 | { 46 | return $this->private; 47 | } 48 | 49 | /** 50 | * Get the public key. 51 | * 52 | * @return PublicKey 53 | */ 54 | public function getPublicKey() : PublicKey 55 | { 56 | if ($this->public === null) { 57 | $this->public = $this->private->getPublicKey(); 58 | } 59 | 60 | return $this->public; 61 | } 62 | 63 | /** 64 | * Check whether the $private key matches the public key. 65 | * 66 | * @param PrivateKey $private 67 | * @param PublicKey $public 68 | * 69 | * @return bool 70 | */ 71 | private function isValidKeyPair(PrivateKey $private, PublicKey $public = null) : bool 72 | { 73 | // If no public key was supplied, the public key will be extracted from the private key. 74 | if ($public === null) { 75 | return true; 76 | } 77 | 78 | // If the public key extracted from the private key matches the supplied public key, the pair must be valid. 79 | if ($private->getPublicKey() == $public) { 80 | return true; 81 | } 82 | 83 | return false; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Client.php: -------------------------------------------------------------------------------- 1 | pushService = $pushService; 38 | $this->httpClient = $httpClient ?? new HttpClient(); 39 | $this->cryptograph = $cryptograph ?? new Cryptograph(new AggregateCrypt()); 40 | } 41 | 42 | /** 43 | * Send a push notification. 44 | * 45 | * @param PushNotification $notification 46 | * @param PushSubscription $subscription 47 | * @param int $ttl 48 | * 49 | * @return ResponseInterface 50 | */ 51 | public function pushNotification(PushNotification $notification, PushSubscription $subscription, int $ttl = 3600) : ResponseInterface 52 | { 53 | return $this->pushNotificationAsync($notification, $subscription, $ttl)->wait(); 54 | } 55 | 56 | /** 57 | * Send a push notification asynchronously. 58 | * 59 | * @param PushNotification $notification 60 | * @param PushSubscription $subscription 61 | * @param int $ttl 62 | * 63 | * @return PromiseInterface 64 | */ 65 | public function pushNotificationAsync(PushNotification $notification, PushSubscription $subscription, int $ttl = 3600) : PromiseInterface 66 | { 67 | $cipher = $this->cryptograph->encrypt($notification, $subscription); 68 | 69 | $pushMessage = new Message($cipher, $subscription, $ttl); 70 | 71 | $request = $this->pushService->createRequest($pushMessage, $subscription); 72 | $promise = $this->httpClient->sendAsync($request); 73 | 74 | return $promise; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /spec/Crypto/PublicKeySpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($binaryString); 17 | 18 | $binaryString->getRawBytes()->willReturn('raw_bytes'); 19 | $binaryString->getLength()->willReturn(65); 20 | } 21 | 22 | function it_is_initializable() 23 | { 24 | $this->shouldHaveType(PublicKey::class); 25 | } 26 | 27 | function it_should_implement_keying_material() 28 | { 29 | $this->shouldImplement(KeyingMaterial::class); 30 | } 31 | 32 | function it_should_contain_the_raw_key_material() 33 | { 34 | $this->getRawKeyMaterial()->shouldBeString(); 35 | } 36 | 37 | function it_can_calculate_its_own_length() 38 | { 39 | $this->getLength()->shouldBeInt(); 40 | } 41 | 42 | function it_should_throw_an_exception_if_the_public_key_has_an_invalid_length(BinaryString $binaryString) 43 | { 44 | $binaryString->getLength()->willReturn(64); 45 | $this->shouldThrow('InvalidArgumentException')->duringInstantiation(); 46 | } 47 | 48 | function it_can_be_created_from_a_base64url_encoded_string() 49 | { 50 | $this->beConstructedThrough('createFromBase64UrlEncodedString', [ 51 | 'BEVYVy0G5j5KhryGGTGZNJejRZJRRVa1BLsCFxZQVZBtLFEso1Tkug8Zji3zX7JPoyLzYn7RXMfj3hd6MwLTqsk', 52 | ]); 53 | 54 | $this->shouldHaveType(PublicKey::class); 55 | } 56 | 57 | function it_can_be_created_from_an_ecc_key() 58 | { 59 | $generator = EccFactory::getNistCurves()->generator256(); 60 | $eccPrivateKey = $generator->createPrivateKey(); 61 | 62 | $this->beConstructedThrough('createFromEccKey', [$eccPrivateKey->getPublicKey()]); 63 | 64 | $this->shouldHaveType(PublicKey::class); 65 | } 66 | 67 | function it_can_generate_an_ecc_key() 68 | { 69 | $this->beConstructedThrough('createFromBase64UrlEncodedString', [ 70 | 'BEVYVy0G5j5KhryGGTGZNJejRZJRRVa1BLsCFxZQVZBtLFEso1Tkug8Zji3zX7JPoyLzYn7RXMfj3hd6MwLTqsk', 71 | ]); 72 | 73 | $this->getEccKey()->shouldReturnAnInstanceOf(PublicKeyInterface::class); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Message.php: -------------------------------------------------------------------------------- 1 | cipher = $cipher; 34 | $this->subscription = $subscription; 35 | $this->ttl = $ttl; 36 | } 37 | 38 | /** 39 | * Get message body. 40 | * 41 | * The message body consists of the cipher text and the authentication tag. 42 | * 43 | * @return string 44 | */ 45 | public function getBody() : string 46 | { 47 | return 48 | $this->cipher->getCipherText()->getRawBytes() 49 | . $this->cipher->getAuthenticationTag()->getRawBytes() 50 | ; 51 | } 52 | 53 | /** 54 | * Get cipher salt. 55 | * 56 | * @return string A base64url encoded representation of the salt used. 57 | */ 58 | public function getSalt() : string 59 | { 60 | return $this->cipher->getSalt()->getBase64UrlEncodedString(); 61 | } 62 | 63 | /** 64 | * Get the public key. 65 | * 66 | * @return string A base64url encoded representation of the public key. 67 | */ 68 | public function getCryptoKey() : string 69 | { 70 | return $this->cipher->getPublicKey()->getBase64UrlEncodedString(); 71 | } 72 | 73 | /** 74 | * Get message content length. 75 | * 76 | * @return int 77 | */ 78 | public function getContentLength() : int 79 | { 80 | return strlen($this->getBody()); 81 | } 82 | 83 | /** 84 | * Get message subscription. 85 | * 86 | * @return PushSubscription 87 | */ 88 | public function getPushSubscription() : PushSubscription 89 | { 90 | return $this->subscription; 91 | } 92 | 93 | /** 94 | * Get message ttl. 95 | * 96 | * @return int 97 | */ 98 | public function getTTL() : int 99 | { 100 | return $this->ttl; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Crypto/Cryptograph.php: -------------------------------------------------------------------------------- 1 | crypt = $crypt; 29 | $this->length = $length ?? 0; 30 | } 31 | 32 | /** 33 | * Message encryption 34 | * 35 | * @param PushNotification $notification 36 | * @param Subscription $subscription 37 | * 38 | * @return Cipher 39 | */ 40 | public function encrypt(PushNotification $notification, Subscription $subscription) : Cipher 41 | { 42 | $plainText = $this->addPadding($notification->json()); 43 | 44 | $recipientPublicKey = $subscription->getPublicKey(); 45 | $authenticationSecret = $subscription->getAuthenticationSecret(); 46 | 47 | $generator = new KeyGenerator(); 48 | $keyPair = $generator->generateKeyPair(); 49 | $senderPrivateKey = $keyPair->getPrivateKey(); 50 | $senderPublicKey = $keyPair->getPublicKey(); 51 | 52 | $sharedSecret = $senderPrivateKey->calculateSharedSecret($recipientPublicKey); 53 | 54 | $salt = new Salt(); 55 | $info = new Info($recipientPublicKey, $senderPublicKey); 56 | $hkdf = new HKDF(); 57 | 58 | $pseudoRandomKey = new PseudoRandomKey($hkdf($authenticationSecret, $sharedSecret, $info->getContentEncoding('auth'), 32)); 59 | $contentEncryptionKey = new ContentEncryptionKey($hkdf($salt, $pseudoRandomKey, $info->getContentEncoding('aesgcm'), 16)); 60 | $nonce = new Nonce($hkdf($salt, $pseudoRandomKey, $info->getContentEncoding('nonce'), 12)); 61 | 62 | return $this->crypt->encrypt($plainText, $contentEncryptionKey, $nonce, $salt, $senderPublicKey); 63 | } 64 | 65 | /** 66 | * Add padding. 67 | * 68 | * Add padding to the plain text notification. This can be used to hide the length of the message send. 69 | * 70 | * @param string $notification 71 | * 72 | * @return string 73 | */ 74 | private function addPadding(string $notification) : string 75 | { 76 | $length = $this->length; 77 | 78 | return pack('n*', $length).str_pad($notification, $length + 2, chr(0), STR_PAD_LEFT); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /spec/MessageSpec.php: -------------------------------------------------------------------------------- 1 | beConstructedWith($cipher, $subscription, 3600); 21 | } 22 | 23 | function it_is_initializable() 24 | { 25 | $this->shouldHaveType(Message::class); 26 | } 27 | 28 | function it_should_implement_push_message() 29 | { 30 | $this->shouldImplement(PushMessage::class); 31 | } 32 | 33 | function it_should_contain_the_message_body(Cipher $cipher, CipherText $cipherText, AuthenticationTag $tag) 34 | { 35 | $cipher->getCipherText()->willReturn($cipherText); 36 | $cipherText->getRawBytes()->willReturn('raw_bytes'); 37 | $cipher->getAuthenticationTag()->willReturn($tag); 38 | $tag->getRawBytes()->willReturn('raw_bytes'); 39 | 40 | $this->getBody()->shouldBeString(); 41 | } 42 | 43 | function it_should_contain_the_used_salt(Cipher $cipher, Salt $salt) 44 | { 45 | $cipher->getSalt()->willReturn($salt); 46 | $salt->getBase64UrlEncodedString()->willReturn('base64url'); 47 | 48 | $this->getSalt()->shouldBeString(); 49 | } 50 | 51 | function it_should_contain_the_used_public_key(Cipher $cipher, PublicKey $key) 52 | { 53 | $cipher->getPublicKey()->willReturn($key); 54 | $key->getBase64UrlEncodedString()->willReturn('base64url'); 55 | 56 | $this->getCryptoKey()->shouldBeString(); 57 | } 58 | 59 | function it_should_contain_the_content_length(Cipher $cipher, CipherText $cipherText, AuthenticationTag $tag) 60 | { 61 | $cipher->getCipherText()->willReturn($cipherText); 62 | $cipherText->getRawBytes()->willReturn('raw_bytes'); 63 | $cipher->getAuthenticationTag()->willReturn($tag); 64 | $tag->getRawBytes()->willReturn('raw_bytes'); 65 | 66 | $this->getContentLength()->shouldBeInt(); 67 | } 68 | 69 | function it_should_contain_a_push_subscription(PushSubscription $subscription) 70 | { 71 | $this->getPushSubscription()->shouldReturn($subscription); 72 | } 73 | 74 | function it_should_contain_the_time_to_live() 75 | { 76 | $this->getTTL()->shouldBeInt(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/GooglePushService.php: -------------------------------------------------------------------------------- 1 | apiKey = $apiKey; 23 | } 24 | 25 | /** 26 | * Check weather this push service supports a certain host. 27 | * 28 | * @param string $host 29 | * 30 | * @return bool 31 | */ 32 | public function supportsHost(string $host) : bool 33 | { 34 | return in_array($host, [ 35 | 'android.googleapis.com', 36 | 'fcm.googleapis.com', 37 | ]); 38 | } 39 | 40 | /** 41 | * Create a push request. 42 | * 43 | * @param PushMessage $message 44 | * 45 | * @return RequestInterface 46 | */ 47 | public function createRequest(PushMessage $message) : RequestInterface 48 | { 49 | $request = new Request( 50 | 'POST', 51 | $this->getUri($message), 52 | $this->getHeaders($message), 53 | $this->getBody($message) 54 | ); 55 | 56 | return $request; 57 | } 58 | 59 | /** 60 | * Get the request uri 61 | * 62 | * @param PushMessage $message 63 | * 64 | * @return string 65 | */ 66 | private function getUri(PushMessage $message) : string 67 | { 68 | return 'https://fcm.googleapis.com/fcm/send/'.$message->getPushSubscription()->getEndpoint()->getRegistrationId(); 69 | } 70 | 71 | /** 72 | * Get the request headers 73 | * 74 | * @param PushMessage $message 75 | * 76 | * @return string[] 77 | */ 78 | private function getHeaders(PushMessage $message) : array 79 | { 80 | return [ 81 | 'Authorization' => 'key='.$this->apiKey, 82 | 'Content-Type' => 'application/json', 83 | 'Content-Length' => $message->getContentLength(), 84 | 'Encryption' => 'keyid=p256dh;salt='.$message->getSalt(), 85 | 'Crypto-Key' => 'keyid=p256dh;dh='.$message->getCryptoKey(), 86 | 'Content-Encoding' => 'aesgcm', 87 | 'TTL' => $message->getTTL(), 88 | ]; 89 | } 90 | 91 | /** 92 | * Get the request body 93 | * 94 | * @param PushMessage $message 95 | * 96 | * @return string 97 | */ 98 | private function getBody(PushMessage $message) : string 99 | { 100 | return $message->getBody(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Crypto/HKDF.php: -------------------------------------------------------------------------------- 1 | extractAndExpand($salt, $keyingMaterial, $info, $length); 22 | } 23 | 24 | /** 25 | * HMAC-based Extract-and-Expand Key Derivation Function (HKDF) 26 | * 27 | * @see https://tools.ietf.org/html/rfc5869 28 | * 29 | * @param RawBytes $salt 30 | * @param KeyingMaterial $keyingMaterial 31 | * @param string $info 32 | * @param int $length 33 | * 34 | * @return BinaryString 35 | */ 36 | public function extractAndExpand(RawBytes $salt, KeyingMaterial $keyingMaterial, string $info, int $length) : BinaryString 37 | { 38 | $pseudoRandomKey = $this->extract($salt, $keyingMaterial); 39 | $outputKeyingMaterial = $this->expand($pseudoRandomKey, $info, $length); 40 | 41 | return $outputKeyingMaterial; 42 | } 43 | 44 | /** 45 | * Extract 46 | * 47 | * @see https://tools.ietf.org/html/rfc5869#section-2.2 48 | * 49 | * @param RawBytes $salt 50 | * @param KeyingMaterial $keyingMaterial 51 | * 52 | * @return BinaryString 53 | */ 54 | private function extract(RawBytes $salt, KeyingMaterial $keyingMaterial) : BinaryString 55 | { 56 | return new BinaryString(hash_hmac('sha256', $keyingMaterial->getRawKeyMaterial(), $salt->getRawBytes(), true)); 57 | } 58 | 59 | /** 60 | * Expand 61 | * 62 | * @see https://tools.ietf.org/html/rfc5869#section-2.3 63 | * 64 | * @param RawBytes $pseudoRandomKey 65 | * @param string $info 66 | * @param int $length 67 | * 68 | * @return BinaryString 69 | */ 70 | private function expand(RawBytes $pseudoRandomKey, string $info, int $length) : BinaryString 71 | { 72 | $T = new BinaryString(''); 73 | $outputKeyingMaterial = new BinaryString(''); 74 | 75 | for ($blockIndex = 1; $blockIndex <= (int) ceil($length / 32); $blockIndex++) { 76 | $T = new BinaryString(hash_hmac( 77 | 'sha256', 78 | $T->getRawBytes() . $info . chr($blockIndex), 79 | $pseudoRandomKey->getRawBytes(), 80 | true 81 | )); 82 | $outputKeyingMaterial = $outputKeyingMaterial->concat($T); 83 | } 84 | 85 | return $outputKeyingMaterial->slice($length); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Crypto/PublicKey.php: -------------------------------------------------------------------------------- 1 | getLength() != 65) { 27 | throw new InvalidArgumentException('PublicKey could not be created: incorrect length.'); 28 | } 29 | 30 | $this->binaryString = $binaryString; 31 | } 32 | 33 | /** 34 | * Create public key from a base64url encoded string. 35 | * 36 | * @param string $base64UrlEncoded 37 | * 38 | * @return self 39 | * 40 | * @throws InvalidArgumentException When the public key is not the correct length. 41 | */ 42 | public static function createFromBase64UrlEncodedString(string $base64UrlEncoded) : self 43 | { 44 | return new self(BinaryString::createFromBase64UrlEncodedString($base64UrlEncoded)); 45 | } 46 | 47 | /** 48 | * Create a public key from a Mdanter Ecc Public Key. 49 | * 50 | * @param PublicKeyInterface $eccKey 51 | * 52 | * @throws InvalidArgumentException When the public key is not the correct length. 53 | */ 54 | public static function createFromEccKey(PublicKeyInterface $eccKey) : self 55 | { 56 | $math = EccFactory::getAdapter(); 57 | $pointSerializer = new UncompressedPointSerializer($math); 58 | $point = $eccKey->getPoint(); 59 | $hex = $pointSerializer->serialize($point); 60 | 61 | $binary = new BinaryString(hex2bin($hex)); 62 | 63 | return new self($binary); 64 | } 65 | 66 | /** 67 | * Get raw bytes. 68 | * 69 | * @return string 70 | */ 71 | public function getRawKeyMaterial() : string 72 | { 73 | return $this->binaryString->getRawBytes(); 74 | } 75 | 76 | /** 77 | * Get base64url encoded string. 78 | * 79 | * @return string 80 | */ 81 | public function getBase64UrlEncodedString() : string 82 | { 83 | return $this->binaryString->getBase64UrlEncodedString(); 84 | } 85 | 86 | /** 87 | * Unserialize the raw key material into a Mdanter Ecc Public Key. 88 | * 89 | * @return PublicKeyInterface 90 | */ 91 | public function getEccKey() : PublicKeyInterface 92 | { 93 | $math = EccFactory::getAdapter(); 94 | $generator = EccFactory::getNistCurves()->generator256(); 95 | $pointSerializer = new UncompressedPointSerializer($math); 96 | $point = $pointSerializer->unserialize($generator->getCurve(), bin2hex($this->getRawKeyMaterial())); 97 | 98 | return $generator->getPublicKeyFrom($point->getX(), $point->getY(), $generator->getOrder()); 99 | } 100 | 101 | /** 102 | * Get the length of the public key. 103 | * 104 | * @return int 105 | */ 106 | public function getLength() : int 107 | { 108 | return $this->binaryString->getLength(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmnty/push 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Software License][ico-license]](LICENSE.md) 5 | [![Total Downloads][ico-downloads]][link-downloads] 6 | 7 | Web Push library for PHP 8 | 9 | ## Instalation 10 | 11 | Require the library with composer: 12 | ```bash 13 | composer require cmnty/push 14 | ``` 15 | 16 | This library supports both `ext-crypto` and `lib-openssl` for it's encryption needs. 17 | While a php fallback can be provided by `spomky-labs/php-aes-gcm` it is advised to use that only as a last resort. 18 | 19 | ## Usage 20 | 21 | ```php 22 | addPushService(new GooglePushService('API Key')); 44 | $pushServiceRegistry->addPushService(new MozillaPushService()); 45 | $pushService = new AggregatePushService($pushServiceRegistry); 46 | $client = new Client($pushService); 47 | 48 | $client->pushNotification($notification, $subscription); 49 | ``` 50 | 51 | By default, the `Cmnty\Push\Crypto\AggregateCrypt` class is used to encrypt the notification. 52 | This class tries to encrypt the notification using third party libraries or extensions in the following order: 53 | * Encrypt using `ext-crypto` implemented by `Cmnty\Push\Crypto\ExtCryptoCrypt` 54 | * Encrypt using `lib-openssl` implemented by `Cmnty\Push\Crypto\OpenSSLCrypt` 55 | * Encrypt using native php implemented by `Cmnty\Push\Crypto\SpomkyLabsCrypt` using `spomky-labs/php-aes-gcm` 56 | 57 | You can also force a certain library or extension to be used by passing it to the PushClient: 58 | ```php 59 | =5.6.0" 27 | }, 28 | "require-dev": { 29 | "phpunit/phpunit": "~4.5", 30 | "satooshi/php-coveralls": "dev-master" 31 | }, 32 | "suggest": { 33 | "php-curl": "For loading OID information from the web if they have not bee defined statically" 34 | }, 35 | "type": "library", 36 | "extra": { 37 | "branch-alias": { 38 | "dev-master": "1.5.x-dev" 39 | } 40 | }, 41 | "autoload": { 42 | "psr-4": { 43 | "FG\\": "lib/" 44 | } 45 | }, 46 | "notification-url": "https://packagist.org/downloads/", 47 | "license": [ 48 | "MIT" 49 | ], 50 | "authors": [ 51 | { 52 | "name": "Friedrich Große", 53 | "email": "friedrich.grosse@gmail.com", 54 | "homepage": "https://github.com/FGrosse", 55 | "role": "Author" 56 | }, 57 | { 58 | "name": "All contributors", 59 | "homepage": "https://github.com/FGrosse/PHPASN1/contributors" 60 | } 61 | ], 62 | "description": "A PHP Framework that allows you to encode and decode arbitrary ASN.1 structures using the ITU-T X.690 Encoding Rules.", 63 | "homepage": "https://github.com/FGrosse/PHPASN1", 64 | "keywords": [ 65 | "DER", 66 | "asn.1", 67 | "asn1", 68 | "ber", 69 | "binary", 70 | "decoding", 71 | "encoding", 72 | "x.509", 73 | "x.690", 74 | "x509", 75 | "x690" 76 | ], 77 | "time": "2016-10-29 15:46:46" 78 | }, 79 | { 80 | "name": "guzzlehttp/guzzle", 81 | "version": "6.2.2", 82 | "source": { 83 | "type": "git", 84 | "url": "https://github.com/guzzle/guzzle.git", 85 | "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60" 86 | }, 87 | "dist": { 88 | "type": "zip", 89 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/ebf29dee597f02f09f4d5bbecc68230ea9b08f60", 90 | "reference": "ebf29dee597f02f09f4d5bbecc68230ea9b08f60", 91 | "shasum": "" 92 | }, 93 | "require": { 94 | "guzzlehttp/promises": "^1.0", 95 | "guzzlehttp/psr7": "^1.3.1", 96 | "php": ">=5.5" 97 | }, 98 | "require-dev": { 99 | "ext-curl": "*", 100 | "phpunit/phpunit": "^4.0", 101 | "psr/log": "^1.0" 102 | }, 103 | "type": "library", 104 | "extra": { 105 | "branch-alias": { 106 | "dev-master": "6.2-dev" 107 | } 108 | }, 109 | "autoload": { 110 | "files": [ 111 | "src/functions_include.php" 112 | ], 113 | "psr-4": { 114 | "GuzzleHttp\\": "src/" 115 | } 116 | }, 117 | "notification-url": "https://packagist.org/downloads/", 118 | "license": [ 119 | "MIT" 120 | ], 121 | "authors": [ 122 | { 123 | "name": "Michael Dowling", 124 | "email": "mtdowling@gmail.com", 125 | "homepage": "https://github.com/mtdowling" 126 | } 127 | ], 128 | "description": "Guzzle is a PHP HTTP client library", 129 | "homepage": "http://guzzlephp.org/", 130 | "keywords": [ 131 | "client", 132 | "curl", 133 | "framework", 134 | "http", 135 | "http client", 136 | "rest", 137 | "web service" 138 | ], 139 | "time": "2016-10-08 15:01:37" 140 | }, 141 | { 142 | "name": "guzzlehttp/promises", 143 | "version": "1.3.0", 144 | "source": { 145 | "type": "git", 146 | "url": "https://github.com/guzzle/promises.git", 147 | "reference": "2693c101803ca78b27972d84081d027fca790a1e" 148 | }, 149 | "dist": { 150 | "type": "zip", 151 | "url": "https://api.github.com/repos/guzzle/promises/zipball/2693c101803ca78b27972d84081d027fca790a1e", 152 | "reference": "2693c101803ca78b27972d84081d027fca790a1e", 153 | "shasum": "" 154 | }, 155 | "require": { 156 | "php": ">=5.5.0" 157 | }, 158 | "require-dev": { 159 | "phpunit/phpunit": "~4.0" 160 | }, 161 | "type": "library", 162 | "extra": { 163 | "branch-alias": { 164 | "dev-master": "1.0-dev" 165 | } 166 | }, 167 | "autoload": { 168 | "psr-4": { 169 | "GuzzleHttp\\Promise\\": "src/" 170 | }, 171 | "files": [ 172 | "src/functions_include.php" 173 | ] 174 | }, 175 | "notification-url": "https://packagist.org/downloads/", 176 | "license": [ 177 | "MIT" 178 | ], 179 | "authors": [ 180 | { 181 | "name": "Michael Dowling", 182 | "email": "mtdowling@gmail.com", 183 | "homepage": "https://github.com/mtdowling" 184 | } 185 | ], 186 | "description": "Guzzle promises library", 187 | "keywords": [ 188 | "promise" 189 | ], 190 | "time": "2016-11-18 17:47:58" 191 | }, 192 | { 193 | "name": "guzzlehttp/psr7", 194 | "version": "1.3.1", 195 | "source": { 196 | "type": "git", 197 | "url": "https://github.com/guzzle/psr7.git", 198 | "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b" 199 | }, 200 | "dist": { 201 | "type": "zip", 202 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", 203 | "reference": "5c6447c9df362e8f8093bda8f5d8873fe5c7f65b", 204 | "shasum": "" 205 | }, 206 | "require": { 207 | "php": ">=5.4.0", 208 | "psr/http-message": "~1.0" 209 | }, 210 | "provide": { 211 | "psr/http-message-implementation": "1.0" 212 | }, 213 | "require-dev": { 214 | "phpunit/phpunit": "~4.0" 215 | }, 216 | "type": "library", 217 | "extra": { 218 | "branch-alias": { 219 | "dev-master": "1.4-dev" 220 | } 221 | }, 222 | "autoload": { 223 | "psr-4": { 224 | "GuzzleHttp\\Psr7\\": "src/" 225 | }, 226 | "files": [ 227 | "src/functions_include.php" 228 | ] 229 | }, 230 | "notification-url": "https://packagist.org/downloads/", 231 | "license": [ 232 | "MIT" 233 | ], 234 | "authors": [ 235 | { 236 | "name": "Michael Dowling", 237 | "email": "mtdowling@gmail.com", 238 | "homepage": "https://github.com/mtdowling" 239 | } 240 | ], 241 | "description": "PSR-7 message implementation", 242 | "keywords": [ 243 | "http", 244 | "message", 245 | "stream", 246 | "uri" 247 | ], 248 | "time": "2016-06-24 23:00:38" 249 | }, 250 | { 251 | "name": "mdanter/ecc", 252 | "version": "v0.4.1", 253 | "source": { 254 | "type": "git", 255 | "url": "https://github.com/phpecc/phpecc.git", 256 | "reference": "15b47485bc3f75014d1296526cfc94cbd5175329" 257 | }, 258 | "dist": { 259 | "type": "zip", 260 | "url": "https://api.github.com/repos/phpecc/phpecc/zipball/15b47485bc3f75014d1296526cfc94cbd5175329", 261 | "reference": "15b47485bc3f75014d1296526cfc94cbd5175329", 262 | "shasum": "" 263 | }, 264 | "require": { 265 | "ext-gmp": "*", 266 | "fgrosse/phpasn1": "~1.5", 267 | "paragonie/random_compat": "^1|^2", 268 | "php": ">=5.6.0" 269 | }, 270 | "require-dev": { 271 | "phpunit/phpunit": "~4.1|~5.0", 272 | "squizlabs/php_codesniffer": "~2", 273 | "symfony/yaml": "~2.6|~3.0" 274 | }, 275 | "type": "library", 276 | "autoload": { 277 | "psr-4": { 278 | "Mdanter\\Ecc\\": "src/" 279 | } 280 | }, 281 | "notification-url": "https://packagist.org/downloads/", 282 | "license": [ 283 | "MIT" 284 | ], 285 | "authors": [ 286 | { 287 | "name": "Matyas Danter", 288 | "homepage": "http://matejdanter.com/", 289 | "role": "Author" 290 | }, 291 | { 292 | "name": "Thibaud Fabre", 293 | "email": "thibaud@aztech.io", 294 | "homepage": "http://aztech.io", 295 | "role": "Maintainer" 296 | } 297 | ], 298 | "description": "PHP Elliptic Curve Cryptography library", 299 | "homepage": "https://github.com/phpecc/phpecc", 300 | "keywords": [ 301 | "Diffie", 302 | "ECDSA", 303 | "Hellman", 304 | "curve", 305 | "ecdh", 306 | "elliptic", 307 | "nistp192", 308 | "nistp224", 309 | "nistp256", 310 | "nistp521", 311 | "phpecc", 312 | "secp256k1" 313 | ], 314 | "time": "2016-11-22 09:30:44" 315 | }, 316 | { 317 | "name": "paragonie/random_compat", 318 | "version": "v2.0.4", 319 | "source": { 320 | "type": "git", 321 | "url": "https://github.com/paragonie/random_compat.git", 322 | "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e" 323 | }, 324 | "dist": { 325 | "type": "zip", 326 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", 327 | "reference": "a9b97968bcde1c4de2a5ec6cbd06a0f6c919b46e", 328 | "shasum": "" 329 | }, 330 | "require": { 331 | "php": ">=5.2.0" 332 | }, 333 | "require-dev": { 334 | "phpunit/phpunit": "4.*|5.*" 335 | }, 336 | "suggest": { 337 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." 338 | }, 339 | "type": "library", 340 | "autoload": { 341 | "files": [ 342 | "lib/random.php" 343 | ] 344 | }, 345 | "notification-url": "https://packagist.org/downloads/", 346 | "license": [ 347 | "MIT" 348 | ], 349 | "authors": [ 350 | { 351 | "name": "Paragon Initiative Enterprises", 352 | "email": "security@paragonie.com", 353 | "homepage": "https://paragonie.com" 354 | } 355 | ], 356 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", 357 | "keywords": [ 358 | "csprng", 359 | "pseudorandom", 360 | "random" 361 | ], 362 | "time": "2016-11-07 23:38:38" 363 | }, 364 | { 365 | "name": "psr/http-message", 366 | "version": "1.0.1", 367 | "source": { 368 | "type": "git", 369 | "url": "https://github.com/php-fig/http-message.git", 370 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" 371 | }, 372 | "dist": { 373 | "type": "zip", 374 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", 375 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", 376 | "shasum": "" 377 | }, 378 | "require": { 379 | "php": ">=5.3.0" 380 | }, 381 | "type": "library", 382 | "extra": { 383 | "branch-alias": { 384 | "dev-master": "1.0.x-dev" 385 | } 386 | }, 387 | "autoload": { 388 | "psr-4": { 389 | "Psr\\Http\\Message\\": "src/" 390 | } 391 | }, 392 | "notification-url": "https://packagist.org/downloads/", 393 | "license": [ 394 | "MIT" 395 | ], 396 | "authors": [ 397 | { 398 | "name": "PHP-FIG", 399 | "homepage": "http://www.php-fig.org/" 400 | } 401 | ], 402 | "description": "Common interface for HTTP messages", 403 | "homepage": "https://github.com/php-fig/http-message", 404 | "keywords": [ 405 | "http", 406 | "http-message", 407 | "psr", 408 | "psr-7", 409 | "request", 410 | "response" 411 | ], 412 | "time": "2016-08-06 14:39:51" 413 | }, 414 | { 415 | "name": "spomky-labs/base64url", 416 | "version": "v1.0.2", 417 | "source": { 418 | "type": "git", 419 | "url": "https://github.com/Spomky-Labs/base64url.git", 420 | "reference": "ef6d5fb93894063d9cee996022259fd08d6646ea" 421 | }, 422 | "dist": { 423 | "type": "zip", 424 | "url": "https://api.github.com/repos/Spomky-Labs/base64url/zipball/ef6d5fb93894063d9cee996022259fd08d6646ea", 425 | "reference": "ef6d5fb93894063d9cee996022259fd08d6646ea", 426 | "shasum": "" 427 | }, 428 | "require": { 429 | "php": "^5.3|^7.0" 430 | }, 431 | "require-dev": { 432 | "phpunit/phpunit": "^4.0|^5.0", 433 | "satooshi/php-coveralls": "^1.0" 434 | }, 435 | "type": "library", 436 | "extra": { 437 | "branch-alias": { 438 | "dev-master": "1.0.x-dev" 439 | } 440 | }, 441 | "autoload": { 442 | "psr-4": { 443 | "Base64Url\\": "src/" 444 | } 445 | }, 446 | "notification-url": "https://packagist.org/downloads/", 447 | "license": [ 448 | "MIT" 449 | ], 450 | "authors": [ 451 | { 452 | "name": "Florent Morselli", 453 | "homepage": "https://github.com/Spomky-Labs/base64url/contributors" 454 | } 455 | ], 456 | "description": "Base 64 URL Safe Encoding/decoding PHP Library", 457 | "homepage": "https://github.com/Spomky-Labs/base64url", 458 | "keywords": [ 459 | "base64", 460 | "rfc4648", 461 | "safe", 462 | "url" 463 | ], 464 | "time": "2016-01-21 19:50:30" 465 | } 466 | ], 467 | "packages-dev": [ 468 | { 469 | "name": "beberlei/assert", 470 | "version": "v2.6.8", 471 | "source": { 472 | "type": "git", 473 | "url": "https://github.com/beberlei/assert.git", 474 | "reference": "848c8f0bde97b48d1e159075e20a6667583f3978" 475 | }, 476 | "dist": { 477 | "type": "zip", 478 | "url": "https://api.github.com/repos/beberlei/assert/zipball/848c8f0bde97b48d1e159075e20a6667583f3978", 479 | "reference": "848c8f0bde97b48d1e159075e20a6667583f3978", 480 | "shasum": "" 481 | }, 482 | "require": { 483 | "ext-mbstring": "*", 484 | "php": ">=5.3" 485 | }, 486 | "require-dev": { 487 | "friendsofphp/php-cs-fixer": "^2.0", 488 | "phpunit/phpunit": "@stable" 489 | }, 490 | "type": "library", 491 | "autoload": { 492 | "psr-4": { 493 | "Assert\\": "lib/Assert" 494 | }, 495 | "files": [ 496 | "lib/Assert/functions.php" 497 | ] 498 | }, 499 | "notification-url": "https://packagist.org/downloads/", 500 | "license": [ 501 | "BSD-2-Clause" 502 | ], 503 | "authors": [ 504 | { 505 | "name": "Benjamin Eberlei", 506 | "email": "kontakt@beberlei.de", 507 | "role": "Lead Developer" 508 | }, 509 | { 510 | "name": "Richard Quadling", 511 | "email": "rquadling@gmail.com", 512 | "role": "Collaborator" 513 | } 514 | ], 515 | "description": "Thin assertion library for input validation in business models.", 516 | "keywords": [ 517 | "assert", 518 | "assertion", 519 | "validation" 520 | ], 521 | "time": "2016-12-05 11:33:17" 522 | }, 523 | { 524 | "name": "doctrine/instantiator", 525 | "version": "1.0.5", 526 | "source": { 527 | "type": "git", 528 | "url": "https://github.com/doctrine/instantiator.git", 529 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 530 | }, 531 | "dist": { 532 | "type": "zip", 533 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 534 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 535 | "shasum": "" 536 | }, 537 | "require": { 538 | "php": ">=5.3,<8.0-DEV" 539 | }, 540 | "require-dev": { 541 | "athletic/athletic": "~0.1.8", 542 | "ext-pdo": "*", 543 | "ext-phar": "*", 544 | "phpunit/phpunit": "~4.0", 545 | "squizlabs/php_codesniffer": "~2.0" 546 | }, 547 | "type": "library", 548 | "extra": { 549 | "branch-alias": { 550 | "dev-master": "1.0.x-dev" 551 | } 552 | }, 553 | "autoload": { 554 | "psr-4": { 555 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 556 | } 557 | }, 558 | "notification-url": "https://packagist.org/downloads/", 559 | "license": [ 560 | "MIT" 561 | ], 562 | "authors": [ 563 | { 564 | "name": "Marco Pivetta", 565 | "email": "ocramius@gmail.com", 566 | "homepage": "http://ocramius.github.com/" 567 | } 568 | ], 569 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 570 | "homepage": "https://github.com/doctrine/instantiator", 571 | "keywords": [ 572 | "constructor", 573 | "instantiate" 574 | ], 575 | "time": "2015-06-14 21:17:01" 576 | }, 577 | { 578 | "name": "phpdocumentor/reflection-common", 579 | "version": "1.0", 580 | "source": { 581 | "type": "git", 582 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 583 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" 584 | }, 585 | "dist": { 586 | "type": "zip", 587 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", 588 | "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", 589 | "shasum": "" 590 | }, 591 | "require": { 592 | "php": ">=5.5" 593 | }, 594 | "require-dev": { 595 | "phpunit/phpunit": "^4.6" 596 | }, 597 | "type": "library", 598 | "extra": { 599 | "branch-alias": { 600 | "dev-master": "1.0.x-dev" 601 | } 602 | }, 603 | "autoload": { 604 | "psr-4": { 605 | "phpDocumentor\\Reflection\\": [ 606 | "src" 607 | ] 608 | } 609 | }, 610 | "notification-url": "https://packagist.org/downloads/", 611 | "license": [ 612 | "MIT" 613 | ], 614 | "authors": [ 615 | { 616 | "name": "Jaap van Otterdijk", 617 | "email": "opensource@ijaap.nl" 618 | } 619 | ], 620 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 621 | "homepage": "http://www.phpdoc.org", 622 | "keywords": [ 623 | "FQSEN", 624 | "phpDocumentor", 625 | "phpdoc", 626 | "reflection", 627 | "static analysis" 628 | ], 629 | "time": "2015-12-27 11:43:31" 630 | }, 631 | { 632 | "name": "phpdocumentor/reflection-docblock", 633 | "version": "3.1.1", 634 | "source": { 635 | "type": "git", 636 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 637 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" 638 | }, 639 | "dist": { 640 | "type": "zip", 641 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/8331b5efe816ae05461b7ca1e721c01b46bafb3e", 642 | "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", 643 | "shasum": "" 644 | }, 645 | "require": { 646 | "php": ">=5.5", 647 | "phpdocumentor/reflection-common": "^1.0@dev", 648 | "phpdocumentor/type-resolver": "^0.2.0", 649 | "webmozart/assert": "^1.0" 650 | }, 651 | "require-dev": { 652 | "mockery/mockery": "^0.9.4", 653 | "phpunit/phpunit": "^4.4" 654 | }, 655 | "type": "library", 656 | "autoload": { 657 | "psr-4": { 658 | "phpDocumentor\\Reflection\\": [ 659 | "src/" 660 | ] 661 | } 662 | }, 663 | "notification-url": "https://packagist.org/downloads/", 664 | "license": [ 665 | "MIT" 666 | ], 667 | "authors": [ 668 | { 669 | "name": "Mike van Riel", 670 | "email": "me@mikevanriel.com" 671 | } 672 | ], 673 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 674 | "time": "2016-09-30 07:12:33" 675 | }, 676 | { 677 | "name": "phpdocumentor/type-resolver", 678 | "version": "0.2.1", 679 | "source": { 680 | "type": "git", 681 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 682 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" 683 | }, 684 | "dist": { 685 | "type": "zip", 686 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", 687 | "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", 688 | "shasum": "" 689 | }, 690 | "require": { 691 | "php": ">=5.5", 692 | "phpdocumentor/reflection-common": "^1.0" 693 | }, 694 | "require-dev": { 695 | "mockery/mockery": "^0.9.4", 696 | "phpunit/phpunit": "^5.2||^4.8.24" 697 | }, 698 | "type": "library", 699 | "extra": { 700 | "branch-alias": { 701 | "dev-master": "1.0.x-dev" 702 | } 703 | }, 704 | "autoload": { 705 | "psr-4": { 706 | "phpDocumentor\\Reflection\\": [ 707 | "src/" 708 | ] 709 | } 710 | }, 711 | "notification-url": "https://packagist.org/downloads/", 712 | "license": [ 713 | "MIT" 714 | ], 715 | "authors": [ 716 | { 717 | "name": "Mike van Riel", 718 | "email": "me@mikevanriel.com" 719 | } 720 | ], 721 | "time": "2016-11-25 06:54:22" 722 | }, 723 | { 724 | "name": "phpspec/php-diff", 725 | "version": "v1.1.0", 726 | "source": { 727 | "type": "git", 728 | "url": "https://github.com/phpspec/php-diff.git", 729 | "reference": "0464787bfa7cd13576c5a1e318709768798bec6a" 730 | }, 731 | "dist": { 732 | "type": "zip", 733 | "url": "https://api.github.com/repos/phpspec/php-diff/zipball/0464787bfa7cd13576c5a1e318709768798bec6a", 734 | "reference": "0464787bfa7cd13576c5a1e318709768798bec6a", 735 | "shasum": "" 736 | }, 737 | "type": "library", 738 | "extra": { 739 | "branch-alias": { 740 | "dev-master": "1.0.x-dev" 741 | } 742 | }, 743 | "autoload": { 744 | "psr-0": { 745 | "Diff": "lib/" 746 | } 747 | }, 748 | "notification-url": "https://packagist.org/downloads/", 749 | "license": [ 750 | "BSD-3-Clause" 751 | ], 752 | "authors": [ 753 | { 754 | "name": "Chris Boulton", 755 | "homepage": "http://github.com/chrisboulton" 756 | } 757 | ], 758 | "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", 759 | "time": "2016-04-07 12:29:16" 760 | }, 761 | { 762 | "name": "phpspec/phpspec", 763 | "version": "3.2.2", 764 | "source": { 765 | "type": "git", 766 | "url": "https://github.com/phpspec/phpspec.git", 767 | "reference": "019a113b657aed90a4aa8f5e39696800779b8b2c" 768 | }, 769 | "dist": { 770 | "type": "zip", 771 | "url": "https://api.github.com/repos/phpspec/phpspec/zipball/019a113b657aed90a4aa8f5e39696800779b8b2c", 772 | "reference": "019a113b657aed90a4aa8f5e39696800779b8b2c", 773 | "shasum": "" 774 | }, 775 | "require": { 776 | "doctrine/instantiator": "^1.0.1", 777 | "ext-tokenizer": "*", 778 | "php": "^5.6 || ^7.0", 779 | "phpspec/php-diff": "^1.0.0", 780 | "phpspec/prophecy": "^1.5", 781 | "sebastian/exporter": "^1.0 || ^2.0", 782 | "symfony/console": "^2.7 || ^3.0", 783 | "symfony/event-dispatcher": "^2.7 || ^3.0", 784 | "symfony/finder": "^2.7 || ^3.0", 785 | "symfony/process": "^2.7 || ^3.0", 786 | "symfony/yaml": "^2.7 || ^3.0" 787 | }, 788 | "require-dev": { 789 | "behat/behat": "^3.1", 790 | "ciaranmcnulty/versionbasedtestskipper": "^0.2.1", 791 | "phpunit/phpunit": "^5.4", 792 | "symfony/filesystem": "^3.0" 793 | }, 794 | "suggest": { 795 | "phpspec/nyan-formatters": "Adds Nyan formatters" 796 | }, 797 | "bin": [ 798 | "bin/phpspec" 799 | ], 800 | "type": "library", 801 | "extra": { 802 | "branch-alias": { 803 | "dev-master": "3.0.x-dev" 804 | } 805 | }, 806 | "autoload": { 807 | "psr-0": { 808 | "PhpSpec": "src/" 809 | } 810 | }, 811 | "notification-url": "https://packagist.org/downloads/", 812 | "license": [ 813 | "MIT" 814 | ], 815 | "authors": [ 816 | { 817 | "name": "Konstantin Kudryashov", 818 | "email": "ever.zet@gmail.com", 819 | "homepage": "http://everzet.com" 820 | }, 821 | { 822 | "name": "Marcello Duarte", 823 | "homepage": "http://marcelloduarte.net/" 824 | }, 825 | { 826 | "name": "Ciaran McNulty", 827 | "homepage": "https://ciaranmcnulty.com/" 828 | } 829 | ], 830 | "description": "Specification-oriented BDD framework for PHP 5.6+", 831 | "homepage": "http://phpspec.net/", 832 | "keywords": [ 833 | "BDD", 834 | "SpecBDD", 835 | "TDD", 836 | "spec", 837 | "specification", 838 | "testing", 839 | "tests" 840 | ], 841 | "time": "2016-12-05 13:46:47" 842 | }, 843 | { 844 | "name": "phpspec/prophecy", 845 | "version": "v1.6.2", 846 | "source": { 847 | "type": "git", 848 | "url": "https://github.com/phpspec/prophecy.git", 849 | "reference": "6c52c2722f8460122f96f86346600e1077ce22cb" 850 | }, 851 | "dist": { 852 | "type": "zip", 853 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/6c52c2722f8460122f96f86346600e1077ce22cb", 854 | "reference": "6c52c2722f8460122f96f86346600e1077ce22cb", 855 | "shasum": "" 856 | }, 857 | "require": { 858 | "doctrine/instantiator": "^1.0.2", 859 | "php": "^5.3|^7.0", 860 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", 861 | "sebastian/comparator": "^1.1", 862 | "sebastian/recursion-context": "^1.0|^2.0" 863 | }, 864 | "require-dev": { 865 | "phpspec/phpspec": "^2.0", 866 | "phpunit/phpunit": "^4.8 || ^5.6.5" 867 | }, 868 | "type": "library", 869 | "extra": { 870 | "branch-alias": { 871 | "dev-master": "1.6.x-dev" 872 | } 873 | }, 874 | "autoload": { 875 | "psr-0": { 876 | "Prophecy\\": "src/" 877 | } 878 | }, 879 | "notification-url": "https://packagist.org/downloads/", 880 | "license": [ 881 | "MIT" 882 | ], 883 | "authors": [ 884 | { 885 | "name": "Konstantin Kudryashov", 886 | "email": "ever.zet@gmail.com", 887 | "homepage": "http://everzet.com" 888 | }, 889 | { 890 | "name": "Marcello Duarte", 891 | "email": "marcello.duarte@gmail.com" 892 | } 893 | ], 894 | "description": "Highly opinionated mocking framework for PHP 5.3+", 895 | "homepage": "https://github.com/phpspec/prophecy", 896 | "keywords": [ 897 | "Double", 898 | "Dummy", 899 | "fake", 900 | "mock", 901 | "spy", 902 | "stub" 903 | ], 904 | "time": "2016-11-21 14:58:47" 905 | }, 906 | { 907 | "name": "psr/log", 908 | "version": "1.0.2", 909 | "source": { 910 | "type": "git", 911 | "url": "https://github.com/php-fig/log.git", 912 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" 913 | }, 914 | "dist": { 915 | "type": "zip", 916 | "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 917 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 918 | "shasum": "" 919 | }, 920 | "require": { 921 | "php": ">=5.3.0" 922 | }, 923 | "type": "library", 924 | "extra": { 925 | "branch-alias": { 926 | "dev-master": "1.0.x-dev" 927 | } 928 | }, 929 | "autoload": { 930 | "psr-4": { 931 | "Psr\\Log\\": "Psr/Log/" 932 | } 933 | }, 934 | "notification-url": "https://packagist.org/downloads/", 935 | "license": [ 936 | "MIT" 937 | ], 938 | "authors": [ 939 | { 940 | "name": "PHP-FIG", 941 | "homepage": "http://www.php-fig.org/" 942 | } 943 | ], 944 | "description": "Common interface for logging libraries", 945 | "homepage": "https://github.com/php-fig/log", 946 | "keywords": [ 947 | "log", 948 | "psr", 949 | "psr-3" 950 | ], 951 | "time": "2016-10-10 12:19:37" 952 | }, 953 | { 954 | "name": "sebastian/comparator", 955 | "version": "1.2.2", 956 | "source": { 957 | "type": "git", 958 | "url": "https://github.com/sebastianbergmann/comparator.git", 959 | "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f" 960 | }, 961 | "dist": { 962 | "type": "zip", 963 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/6a1ed12e8b2409076ab22e3897126211ff8b1f7f", 964 | "reference": "6a1ed12e8b2409076ab22e3897126211ff8b1f7f", 965 | "shasum": "" 966 | }, 967 | "require": { 968 | "php": ">=5.3.3", 969 | "sebastian/diff": "~1.2", 970 | "sebastian/exporter": "~1.2 || ~2.0" 971 | }, 972 | "require-dev": { 973 | "phpunit/phpunit": "~4.4" 974 | }, 975 | "type": "library", 976 | "extra": { 977 | "branch-alias": { 978 | "dev-master": "1.2.x-dev" 979 | } 980 | }, 981 | "autoload": { 982 | "classmap": [ 983 | "src/" 984 | ] 985 | }, 986 | "notification-url": "https://packagist.org/downloads/", 987 | "license": [ 988 | "BSD-3-Clause" 989 | ], 990 | "authors": [ 991 | { 992 | "name": "Jeff Welch", 993 | "email": "whatthejeff@gmail.com" 994 | }, 995 | { 996 | "name": "Volker Dusch", 997 | "email": "github@wallbash.com" 998 | }, 999 | { 1000 | "name": "Bernhard Schussek", 1001 | "email": "bschussek@2bepublished.at" 1002 | }, 1003 | { 1004 | "name": "Sebastian Bergmann", 1005 | "email": "sebastian@phpunit.de" 1006 | } 1007 | ], 1008 | "description": "Provides the functionality to compare PHP values for equality", 1009 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1010 | "keywords": [ 1011 | "comparator", 1012 | "compare", 1013 | "equality" 1014 | ], 1015 | "time": "2016-11-19 09:18:40" 1016 | }, 1017 | { 1018 | "name": "sebastian/diff", 1019 | "version": "1.4.1", 1020 | "source": { 1021 | "type": "git", 1022 | "url": "https://github.com/sebastianbergmann/diff.git", 1023 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" 1024 | }, 1025 | "dist": { 1026 | "type": "zip", 1027 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", 1028 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", 1029 | "shasum": "" 1030 | }, 1031 | "require": { 1032 | "php": ">=5.3.3" 1033 | }, 1034 | "require-dev": { 1035 | "phpunit/phpunit": "~4.8" 1036 | }, 1037 | "type": "library", 1038 | "extra": { 1039 | "branch-alias": { 1040 | "dev-master": "1.4-dev" 1041 | } 1042 | }, 1043 | "autoload": { 1044 | "classmap": [ 1045 | "src/" 1046 | ] 1047 | }, 1048 | "notification-url": "https://packagist.org/downloads/", 1049 | "license": [ 1050 | "BSD-3-Clause" 1051 | ], 1052 | "authors": [ 1053 | { 1054 | "name": "Kore Nordmann", 1055 | "email": "mail@kore-nordmann.de" 1056 | }, 1057 | { 1058 | "name": "Sebastian Bergmann", 1059 | "email": "sebastian@phpunit.de" 1060 | } 1061 | ], 1062 | "description": "Diff implementation", 1063 | "homepage": "https://github.com/sebastianbergmann/diff", 1064 | "keywords": [ 1065 | "diff" 1066 | ], 1067 | "time": "2015-12-08 07:14:41" 1068 | }, 1069 | { 1070 | "name": "sebastian/exporter", 1071 | "version": "2.0.0", 1072 | "source": { 1073 | "type": "git", 1074 | "url": "https://github.com/sebastianbergmann/exporter.git", 1075 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4" 1076 | }, 1077 | "dist": { 1078 | "type": "zip", 1079 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 1080 | "reference": "ce474bdd1a34744d7ac5d6aad3a46d48d9bac4c4", 1081 | "shasum": "" 1082 | }, 1083 | "require": { 1084 | "php": ">=5.3.3", 1085 | "sebastian/recursion-context": "~2.0" 1086 | }, 1087 | "require-dev": { 1088 | "ext-mbstring": "*", 1089 | "phpunit/phpunit": "~4.4" 1090 | }, 1091 | "type": "library", 1092 | "extra": { 1093 | "branch-alias": { 1094 | "dev-master": "2.0.x-dev" 1095 | } 1096 | }, 1097 | "autoload": { 1098 | "classmap": [ 1099 | "src/" 1100 | ] 1101 | }, 1102 | "notification-url": "https://packagist.org/downloads/", 1103 | "license": [ 1104 | "BSD-3-Clause" 1105 | ], 1106 | "authors": [ 1107 | { 1108 | "name": "Jeff Welch", 1109 | "email": "whatthejeff@gmail.com" 1110 | }, 1111 | { 1112 | "name": "Volker Dusch", 1113 | "email": "github@wallbash.com" 1114 | }, 1115 | { 1116 | "name": "Bernhard Schussek", 1117 | "email": "bschussek@2bepublished.at" 1118 | }, 1119 | { 1120 | "name": "Sebastian Bergmann", 1121 | "email": "sebastian@phpunit.de" 1122 | }, 1123 | { 1124 | "name": "Adam Harvey", 1125 | "email": "aharvey@php.net" 1126 | } 1127 | ], 1128 | "description": "Provides the functionality to export PHP variables for visualization", 1129 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1130 | "keywords": [ 1131 | "export", 1132 | "exporter" 1133 | ], 1134 | "time": "2016-11-19 08:54:04" 1135 | }, 1136 | { 1137 | "name": "sebastian/recursion-context", 1138 | "version": "2.0.0", 1139 | "source": { 1140 | "type": "git", 1141 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1142 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a" 1143 | }, 1144 | "dist": { 1145 | "type": "zip", 1146 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1147 | "reference": "2c3ba150cbec723aa057506e73a8d33bdb286c9a", 1148 | "shasum": "" 1149 | }, 1150 | "require": { 1151 | "php": ">=5.3.3" 1152 | }, 1153 | "require-dev": { 1154 | "phpunit/phpunit": "~4.4" 1155 | }, 1156 | "type": "library", 1157 | "extra": { 1158 | "branch-alias": { 1159 | "dev-master": "2.0.x-dev" 1160 | } 1161 | }, 1162 | "autoload": { 1163 | "classmap": [ 1164 | "src/" 1165 | ] 1166 | }, 1167 | "notification-url": "https://packagist.org/downloads/", 1168 | "license": [ 1169 | "BSD-3-Clause" 1170 | ], 1171 | "authors": [ 1172 | { 1173 | "name": "Jeff Welch", 1174 | "email": "whatthejeff@gmail.com" 1175 | }, 1176 | { 1177 | "name": "Sebastian Bergmann", 1178 | "email": "sebastian@phpunit.de" 1179 | }, 1180 | { 1181 | "name": "Adam Harvey", 1182 | "email": "aharvey@php.net" 1183 | } 1184 | ], 1185 | "description": "Provides functionality to recursively process PHP variables", 1186 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1187 | "time": "2016-11-19 07:33:16" 1188 | }, 1189 | { 1190 | "name": "spomky-labs/php-aes-gcm", 1191 | "version": "v1.2.0", 1192 | "source": { 1193 | "type": "git", 1194 | "url": "https://github.com/Spomky-Labs/php-aes-gcm.git", 1195 | "reference": "b655bef0d4f0fa2f36c11c5122e284951e81961c" 1196 | }, 1197 | "dist": { 1198 | "type": "zip", 1199 | "url": "https://api.github.com/repos/Spomky-Labs/php-aes-gcm/zipball/b655bef0d4f0fa2f36c11c5122e284951e81961c", 1200 | "reference": "b655bef0d4f0fa2f36c11c5122e284951e81961c", 1201 | "shasum": "" 1202 | }, 1203 | "require": { 1204 | "beberlei/assert": "^2.4", 1205 | "lib-openssl": "*", 1206 | "php": ">=5.4", 1207 | "symfony/polyfill-mbstring": "^1.1" 1208 | }, 1209 | "require-dev": { 1210 | "phpunit/phpunit": "^4.5|^5.0", 1211 | "satooshi/php-coveralls": "^1.0" 1212 | }, 1213 | "suggest": { 1214 | "ext-crypto": "Highly recommended for better performance." 1215 | }, 1216 | "type": "library", 1217 | "extra": { 1218 | "branch-alias": { 1219 | "dev-master": "1.2.x-dev" 1220 | } 1221 | }, 1222 | "autoload": { 1223 | "psr-4": { 1224 | "AESGCM\\": "src/" 1225 | } 1226 | }, 1227 | "notification-url": "https://packagist.org/downloads/", 1228 | "license": [ 1229 | "MIT" 1230 | ], 1231 | "authors": [ 1232 | { 1233 | "name": "Florent Morselli", 1234 | "homepage": "https://github.com/Spomky" 1235 | }, 1236 | { 1237 | "name": "All contributors", 1238 | "homepage": "https://github.com/Spomky-Labs/php-aes-gcm/contributors" 1239 | } 1240 | ], 1241 | "description": "AES GCM (Galois Counter Mode) PHP implementation.", 1242 | "homepage": "https://github.com/Spomky-Labs/php-aes-gcm", 1243 | "keywords": [ 1244 | "AES-GCM", 1245 | "Galois Counter Mode", 1246 | "aes", 1247 | "gcm" 1248 | ], 1249 | "time": "2016-11-22 21:11:11" 1250 | }, 1251 | { 1252 | "name": "symfony/console", 1253 | "version": "v3.2.1", 1254 | "source": { 1255 | "type": "git", 1256 | "url": "https://github.com/symfony/console.git", 1257 | "reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa" 1258 | }, 1259 | "dist": { 1260 | "type": "zip", 1261 | "url": "https://api.github.com/repos/symfony/console/zipball/d12aa9ca20f4db83ec58410978dab6afcb9d6aaa", 1262 | "reference": "d12aa9ca20f4db83ec58410978dab6afcb9d6aaa", 1263 | "shasum": "" 1264 | }, 1265 | "require": { 1266 | "php": ">=5.5.9", 1267 | "symfony/debug": "~2.8|~3.0", 1268 | "symfony/polyfill-mbstring": "~1.0" 1269 | }, 1270 | "require-dev": { 1271 | "psr/log": "~1.0", 1272 | "symfony/event-dispatcher": "~2.8|~3.0", 1273 | "symfony/filesystem": "~2.8|~3.0", 1274 | "symfony/process": "~2.8|~3.0" 1275 | }, 1276 | "suggest": { 1277 | "psr/log": "For using the console logger", 1278 | "symfony/event-dispatcher": "", 1279 | "symfony/filesystem": "", 1280 | "symfony/process": "" 1281 | }, 1282 | "type": "library", 1283 | "extra": { 1284 | "branch-alias": { 1285 | "dev-master": "3.2-dev" 1286 | } 1287 | }, 1288 | "autoload": { 1289 | "psr-4": { 1290 | "Symfony\\Component\\Console\\": "" 1291 | }, 1292 | "exclude-from-classmap": [ 1293 | "/Tests/" 1294 | ] 1295 | }, 1296 | "notification-url": "https://packagist.org/downloads/", 1297 | "license": [ 1298 | "MIT" 1299 | ], 1300 | "authors": [ 1301 | { 1302 | "name": "Fabien Potencier", 1303 | "email": "fabien@symfony.com" 1304 | }, 1305 | { 1306 | "name": "Symfony Community", 1307 | "homepage": "https://symfony.com/contributors" 1308 | } 1309 | ], 1310 | "description": "Symfony Console Component", 1311 | "homepage": "https://symfony.com", 1312 | "time": "2016-12-11 14:34:22" 1313 | }, 1314 | { 1315 | "name": "symfony/debug", 1316 | "version": "v3.2.1", 1317 | "source": { 1318 | "type": "git", 1319 | "url": "https://github.com/symfony/debug.git", 1320 | "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231" 1321 | }, 1322 | "dist": { 1323 | "type": "zip", 1324 | "url": "https://api.github.com/repos/symfony/debug/zipball/9f923e68d524a3095c5a2ae5fc7220c7cbc12231", 1325 | "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231", 1326 | "shasum": "" 1327 | }, 1328 | "require": { 1329 | "php": ">=5.5.9", 1330 | "psr/log": "~1.0" 1331 | }, 1332 | "conflict": { 1333 | "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" 1334 | }, 1335 | "require-dev": { 1336 | "symfony/class-loader": "~2.8|~3.0", 1337 | "symfony/http-kernel": "~2.8|~3.0" 1338 | }, 1339 | "type": "library", 1340 | "extra": { 1341 | "branch-alias": { 1342 | "dev-master": "3.2-dev" 1343 | } 1344 | }, 1345 | "autoload": { 1346 | "psr-4": { 1347 | "Symfony\\Component\\Debug\\": "" 1348 | }, 1349 | "exclude-from-classmap": [ 1350 | "/Tests/" 1351 | ] 1352 | }, 1353 | "notification-url": "https://packagist.org/downloads/", 1354 | "license": [ 1355 | "MIT" 1356 | ], 1357 | "authors": [ 1358 | { 1359 | "name": "Fabien Potencier", 1360 | "email": "fabien@symfony.com" 1361 | }, 1362 | { 1363 | "name": "Symfony Community", 1364 | "homepage": "https://symfony.com/contributors" 1365 | } 1366 | ], 1367 | "description": "Symfony Debug Component", 1368 | "homepage": "https://symfony.com", 1369 | "time": "2016-11-16 22:18:16" 1370 | }, 1371 | { 1372 | "name": "symfony/event-dispatcher", 1373 | "version": "v3.2.1", 1374 | "source": { 1375 | "type": "git", 1376 | "url": "https://github.com/symfony/event-dispatcher.git", 1377 | "reference": "e8f47a327c2f0fd5aa04fa60af2b693006ed7283" 1378 | }, 1379 | "dist": { 1380 | "type": "zip", 1381 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e8f47a327c2f0fd5aa04fa60af2b693006ed7283", 1382 | "reference": "e8f47a327c2f0fd5aa04fa60af2b693006ed7283", 1383 | "shasum": "" 1384 | }, 1385 | "require": { 1386 | "php": ">=5.5.9" 1387 | }, 1388 | "require-dev": { 1389 | "psr/log": "~1.0", 1390 | "symfony/config": "~2.8|~3.0", 1391 | "symfony/dependency-injection": "~2.8|~3.0", 1392 | "symfony/expression-language": "~2.8|~3.0", 1393 | "symfony/stopwatch": "~2.8|~3.0" 1394 | }, 1395 | "suggest": { 1396 | "symfony/dependency-injection": "", 1397 | "symfony/http-kernel": "" 1398 | }, 1399 | "type": "library", 1400 | "extra": { 1401 | "branch-alias": { 1402 | "dev-master": "3.2-dev" 1403 | } 1404 | }, 1405 | "autoload": { 1406 | "psr-4": { 1407 | "Symfony\\Component\\EventDispatcher\\": "" 1408 | }, 1409 | "exclude-from-classmap": [ 1410 | "/Tests/" 1411 | ] 1412 | }, 1413 | "notification-url": "https://packagist.org/downloads/", 1414 | "license": [ 1415 | "MIT" 1416 | ], 1417 | "authors": [ 1418 | { 1419 | "name": "Fabien Potencier", 1420 | "email": "fabien@symfony.com" 1421 | }, 1422 | { 1423 | "name": "Symfony Community", 1424 | "homepage": "https://symfony.com/contributors" 1425 | } 1426 | ], 1427 | "description": "Symfony EventDispatcher Component", 1428 | "homepage": "https://symfony.com", 1429 | "time": "2016-10-13 06:29:04" 1430 | }, 1431 | { 1432 | "name": "symfony/finder", 1433 | "version": "v3.2.1", 1434 | "source": { 1435 | "type": "git", 1436 | "url": "https://github.com/symfony/finder.git", 1437 | "reference": "a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b" 1438 | }, 1439 | "dist": { 1440 | "type": "zip", 1441 | "url": "https://api.github.com/repos/symfony/finder/zipball/a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b", 1442 | "reference": "a69cb5d455b4885ca376dc5bb3e1155cc8c08c4b", 1443 | "shasum": "" 1444 | }, 1445 | "require": { 1446 | "php": ">=5.5.9" 1447 | }, 1448 | "type": "library", 1449 | "extra": { 1450 | "branch-alias": { 1451 | "dev-master": "3.2-dev" 1452 | } 1453 | }, 1454 | "autoload": { 1455 | "psr-4": { 1456 | "Symfony\\Component\\Finder\\": "" 1457 | }, 1458 | "exclude-from-classmap": [ 1459 | "/Tests/" 1460 | ] 1461 | }, 1462 | "notification-url": "https://packagist.org/downloads/", 1463 | "license": [ 1464 | "MIT" 1465 | ], 1466 | "authors": [ 1467 | { 1468 | "name": "Fabien Potencier", 1469 | "email": "fabien@symfony.com" 1470 | }, 1471 | { 1472 | "name": "Symfony Community", 1473 | "homepage": "https://symfony.com/contributors" 1474 | } 1475 | ], 1476 | "description": "Symfony Finder Component", 1477 | "homepage": "https://symfony.com", 1478 | "time": "2016-12-13 09:39:43" 1479 | }, 1480 | { 1481 | "name": "symfony/polyfill-mbstring", 1482 | "version": "v1.3.0", 1483 | "source": { 1484 | "type": "git", 1485 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1486 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" 1487 | }, 1488 | "dist": { 1489 | "type": "zip", 1490 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/e79d363049d1c2128f133a2667e4f4190904f7f4", 1491 | "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", 1492 | "shasum": "" 1493 | }, 1494 | "require": { 1495 | "php": ">=5.3.3" 1496 | }, 1497 | "suggest": { 1498 | "ext-mbstring": "For best performance" 1499 | }, 1500 | "type": "library", 1501 | "extra": { 1502 | "branch-alias": { 1503 | "dev-master": "1.3-dev" 1504 | } 1505 | }, 1506 | "autoload": { 1507 | "psr-4": { 1508 | "Symfony\\Polyfill\\Mbstring\\": "" 1509 | }, 1510 | "files": [ 1511 | "bootstrap.php" 1512 | ] 1513 | }, 1514 | "notification-url": "https://packagist.org/downloads/", 1515 | "license": [ 1516 | "MIT" 1517 | ], 1518 | "authors": [ 1519 | { 1520 | "name": "Nicolas Grekas", 1521 | "email": "p@tchwork.com" 1522 | }, 1523 | { 1524 | "name": "Symfony Community", 1525 | "homepage": "https://symfony.com/contributors" 1526 | } 1527 | ], 1528 | "description": "Symfony polyfill for the Mbstring extension", 1529 | "homepage": "https://symfony.com", 1530 | "keywords": [ 1531 | "compatibility", 1532 | "mbstring", 1533 | "polyfill", 1534 | "portable", 1535 | "shim" 1536 | ], 1537 | "time": "2016-11-14 01:06:16" 1538 | }, 1539 | { 1540 | "name": "symfony/process", 1541 | "version": "v3.2.1", 1542 | "source": { 1543 | "type": "git", 1544 | "url": "https://github.com/symfony/process.git", 1545 | "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3" 1546 | }, 1547 | "dist": { 1548 | "type": "zip", 1549 | "url": "https://api.github.com/repos/symfony/process/zipball/02ea84847aad71be7e32056408bb19f3a616cdd3", 1550 | "reference": "02ea84847aad71be7e32056408bb19f3a616cdd3", 1551 | "shasum": "" 1552 | }, 1553 | "require": { 1554 | "php": ">=5.5.9" 1555 | }, 1556 | "type": "library", 1557 | "extra": { 1558 | "branch-alias": { 1559 | "dev-master": "3.2-dev" 1560 | } 1561 | }, 1562 | "autoload": { 1563 | "psr-4": { 1564 | "Symfony\\Component\\Process\\": "" 1565 | }, 1566 | "exclude-from-classmap": [ 1567 | "/Tests/" 1568 | ] 1569 | }, 1570 | "notification-url": "https://packagist.org/downloads/", 1571 | "license": [ 1572 | "MIT" 1573 | ], 1574 | "authors": [ 1575 | { 1576 | "name": "Fabien Potencier", 1577 | "email": "fabien@symfony.com" 1578 | }, 1579 | { 1580 | "name": "Symfony Community", 1581 | "homepage": "https://symfony.com/contributors" 1582 | } 1583 | ], 1584 | "description": "Symfony Process Component", 1585 | "homepage": "https://symfony.com", 1586 | "time": "2016-11-24 10:40:28" 1587 | }, 1588 | { 1589 | "name": "symfony/yaml", 1590 | "version": "v3.2.1", 1591 | "source": { 1592 | "type": "git", 1593 | "url": "https://github.com/symfony/yaml.git", 1594 | "reference": "a7095af4b97a0955f85c8989106c249fa649011f" 1595 | }, 1596 | "dist": { 1597 | "type": "zip", 1598 | "url": "https://api.github.com/repos/symfony/yaml/zipball/a7095af4b97a0955f85c8989106c249fa649011f", 1599 | "reference": "a7095af4b97a0955f85c8989106c249fa649011f", 1600 | "shasum": "" 1601 | }, 1602 | "require": { 1603 | "php": ">=5.5.9" 1604 | }, 1605 | "require-dev": { 1606 | "symfony/console": "~2.8|~3.0" 1607 | }, 1608 | "suggest": { 1609 | "symfony/console": "For validating YAML files using the lint command" 1610 | }, 1611 | "type": "library", 1612 | "extra": { 1613 | "branch-alias": { 1614 | "dev-master": "3.2-dev" 1615 | } 1616 | }, 1617 | "autoload": { 1618 | "psr-4": { 1619 | "Symfony\\Component\\Yaml\\": "" 1620 | }, 1621 | "exclude-from-classmap": [ 1622 | "/Tests/" 1623 | ] 1624 | }, 1625 | "notification-url": "https://packagist.org/downloads/", 1626 | "license": [ 1627 | "MIT" 1628 | ], 1629 | "authors": [ 1630 | { 1631 | "name": "Fabien Potencier", 1632 | "email": "fabien@symfony.com" 1633 | }, 1634 | { 1635 | "name": "Symfony Community", 1636 | "homepage": "https://symfony.com/contributors" 1637 | } 1638 | ], 1639 | "description": "Symfony Yaml Component", 1640 | "homepage": "https://symfony.com", 1641 | "time": "2016-12-10 10:07:06" 1642 | }, 1643 | { 1644 | "name": "webmozart/assert", 1645 | "version": "1.2.0", 1646 | "source": { 1647 | "type": "git", 1648 | "url": "https://github.com/webmozart/assert.git", 1649 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" 1650 | }, 1651 | "dist": { 1652 | "type": "zip", 1653 | "url": "https://api.github.com/repos/webmozart/assert/zipball/2db61e59ff05fe5126d152bd0655c9ea113e550f", 1654 | "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", 1655 | "shasum": "" 1656 | }, 1657 | "require": { 1658 | "php": "^5.3.3 || ^7.0" 1659 | }, 1660 | "require-dev": { 1661 | "phpunit/phpunit": "^4.6", 1662 | "sebastian/version": "^1.0.1" 1663 | }, 1664 | "type": "library", 1665 | "extra": { 1666 | "branch-alias": { 1667 | "dev-master": "1.3-dev" 1668 | } 1669 | }, 1670 | "autoload": { 1671 | "psr-4": { 1672 | "Webmozart\\Assert\\": "src/" 1673 | } 1674 | }, 1675 | "notification-url": "https://packagist.org/downloads/", 1676 | "license": [ 1677 | "MIT" 1678 | ], 1679 | "authors": [ 1680 | { 1681 | "name": "Bernhard Schussek", 1682 | "email": "bschussek@gmail.com" 1683 | } 1684 | ], 1685 | "description": "Assertions to validate method input/output with nice error messages.", 1686 | "keywords": [ 1687 | "assert", 1688 | "check", 1689 | "validate" 1690 | ], 1691 | "time": "2016-11-23 20:04:58" 1692 | } 1693 | ], 1694 | "aliases": [], 1695 | "minimum-stability": "stable", 1696 | "stability-flags": [], 1697 | "prefer-stable": false, 1698 | "prefer-lowest": false, 1699 | "platform": { 1700 | "php": "~7.0" 1701 | }, 1702 | "platform-dev": { 1703 | "ext-crypto": "*", 1704 | "lib-openssl": "*" 1705 | } 1706 | } 1707 | --------------------------------------------------------------------------------