├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── broken-links.yml │ ├── sonar.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── resources ├── Certificates │ ├── test_certificate-1024.pem │ ├── test_certificate-2048.der │ ├── test_certificate-2048.pem │ ├── test_certificate-4096.pem │ └── test_certificate-512.pem └── Keys │ ├── Pkcs1 │ ├── test_key_pkcs1-1024.pem │ ├── test_key_pkcs1-2048.pem │ ├── test_key_pkcs1-4096.pem │ └── test_key_pkcs1-512.pem │ ├── Pkcs12 │ └── test_key.p12 │ └── Pkcs8 │ ├── test_invalid_key.der │ ├── test_key_pkcs8-1024.der │ ├── test_key_pkcs8-1024.pem │ ├── test_key_pkcs8-2048.der │ ├── test_key_pkcs8-2048.pem │ ├── test_key_pkcs8-4096.der │ ├── test_key_pkcs8-4096.pem │ ├── test_key_pkcs8-512.der │ └── test_key_pkcs8-512.pem ├── src └── Developer │ ├── Encryption │ ├── AES │ │ ├── AESCBC.php │ │ ├── AESEncryption.php │ │ └── AESGCM.php │ ├── EncryptionConfig.php │ ├── EncryptionConfigBuilder.php │ ├── EncryptionConfigScheme.php │ ├── EncryptionException.php │ ├── FieldLevelEncryption.php │ ├── FieldLevelEncryptionConfig.php │ ├── FieldLevelEncryptionConfigBuilder.php │ ├── FieldLevelEncryptionParams.php │ ├── FieldValueEncoding.php │ ├── JWE │ │ ├── JweHeader.php │ │ └── JweObject.php │ ├── JweConfig.php │ ├── JweConfigBuilder.php │ ├── JweEncryption.php │ └── RSA │ │ └── RSA.php │ ├── Interceptors │ ├── PsrHttpMessageEncryptionAbstractInterceptor.php │ ├── PsrHttpMessageEncryptionInterceptor.php │ ├── PsrHttpMessageJweInterceptor.php │ └── PsrStreamInterfaceImpl.php │ ├── Json │ ├── JsonPath.php │ └── JsonUtils.php │ ├── Keys │ ├── DecryptionKey.php │ └── EncryptionKey.php │ └── Utils │ ├── EncodingUtils.php │ ├── EncryptionUtils.php │ └── StringUtils.php └── tests └── Developer ├── Encryption ├── AES │ ├── AESCBCTest.php │ ├── AESEncryptionTest.php │ └── AESGCMTest.php ├── EncryptionExceptionTest.php ├── FieldLevelEncryptionConfigBuilderTest.php ├── FieldLevelEncryptionTest.php ├── FileEncryptionParamsTest.php ├── JWE │ ├── JweHeaderTest.php │ └── JweObjectTest.php ├── JweConfigBuilderTest.php ├── JweEncryptionTest.php └── RSA │ └── RSATest.php ├── Interceptors ├── PsrHttpMessageFieldLevelEncryptionInterceptorTest.php ├── PsrHttpMessageJweInterceptorTest.php └── PsrStreamInterfaceImplTest.php ├── Json └── JsonPathTest.php ├── Test └── TestUtils.php └── Utils ├── EncodingUtilsTest.php ├── EncryptionUtilsTest.php └── StringUtilsTest.php /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report to help us improve 4 | title: "[BUG] Description" 5 | labels: 'Issue: Bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | #### Bug Report Checklist 11 | 12 | - [ ] Have you provided a code sample to reproduce the issue? 13 | - [ ] Have you tested with the latest release to confirm the issue still exists? 14 | - [ ] Have you searched for related issues/PRs? 15 | - [ ] What's the actual output vs expected output? 16 | 17 | 20 | 21 | **Description** 22 | A clear and concise description of what is the question, suggestion, or issue and why this is a problem for you. 23 | 24 | **To Reproduce** 25 | Steps to reproduce the behavior. 26 | 27 | **Expected behavior** 28 | A clear and concise description of what you expected to happen. 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | 33 | **Additional context** 34 | Add any other context about the problem here (OS, language version, etc..). 35 | 36 | 37 | **Related issues/PRs** 38 | Has a similar issue/PR been reported/opened before? 39 | 40 | **Suggest a fix/enhancement** 41 | If you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit), or simply make a suggestion. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[REQ] Feature Request Description" 5 | labels: 'Enhancement: Feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Is your feature request related to a problem? Please describe. 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | ### Describe the solution you'd like 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ### Describe alternatives you've considered 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ### Additional context 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | ### PR checklist 3 | 4 | - [ ] An issue/feature request has been created for this PR 5 | - [ ] Pull Request title clearly describes the work in the pull request and the Pull Request description provides details about how to validate the work. Missing information here may result in a delayed response. 6 | - [ ] File the PR against the `master` branch 7 | - [ ] The code in this PR is covered by unit tests 8 | 9 | #### Link to issue/feature request: *add the link here* 10 | 11 | #### Description 12 | A clear and concise description of what is this PR for and any additional info might be useful for reviewing it. 13 | -------------------------------------------------------------------------------- /.github/workflows/broken-links.yml: -------------------------------------------------------------------------------- 1 | 'on': 2 | push: 3 | branches: 4 | - "**" 5 | schedule: 6 | - cron: 0 16 * * * 7 | workflow_dispatch: 8 | name: broken links? 9 | jobs: 10 | linkChecker: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Link Checker 15 | id: lc 16 | uses: peter-evans/link-checker@v1.2.2 17 | with: 18 | args: '-v -r *.md' 19 | - name: Fail? 20 | run: 'exit ${{ steps.lc.outputs.exit_code }}' 21 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yml: -------------------------------------------------------------------------------- 1 | name: Sonar 2 | 'on': 3 | push: 4 | branches: 5 | - "**" 6 | pull_request_target: 7 | branches: 8 | - "**" 9 | types: [opened, synchronize, reopened, labeled] 10 | schedule: 11 | - cron: 0 16 * * * 12 | workflow_dispatch: 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v2 18 | with: 19 | fetch-depth: 0 20 | - name: Check for external PR 21 | if: ${{ !(contains(github.event.pull_request.labels.*.name, 'safe') || 22 | github.event.pull_request.head.repo.full_name == github.repository || 23 | github.event_name != 'pull_request_target') }} 24 | run: echo "Unsecure PR, must be labelled with the 'safe' label, then run the workflow again" && exit 1 25 | - name: Install PHP 26 | uses: shivammathur/setup-php@v2 27 | with: 28 | php-version: 7.4 29 | coverage: xdebug 30 | - name: Setup java 31 | uses: actions/setup-java@v1 32 | with: 33 | java-version: '11' 34 | - name: Install dependencies 35 | run: > 36 | wget 37 | https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.3.0.1492.zip 38 | 39 | unzip sonar-scanner-cli-3.3.0.1492.zip 40 | 41 | composer --no-plugins --no-scripts install 42 | 43 | composer --no-plugins --no-scripts dump-autoload -o 44 | 45 | vendor/bin/phpunit --configuration ./phpunit.xml --teamcity 46 | - name: Sonar 47 | env: 48 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 49 | SONAR_TOKEN: '${{ secrets.SONAR_TOKEN }}' 50 | run: | 51 | sonar-scanner-3.3.0.1492/bin/sonar-scanner \ 52 | -Dsonar.projectName=client-encryption-php \ 53 | -Dsonar.projectKey=Mastercard_client-encryption-php \ 54 | -Dsonar.organization=mastercard \ 55 | -Dsonar.sources=./src \ 56 | -Dsonar.tests=./tests \ 57 | -Dsonar.exclusions=**/vendor/**,**/tests/**,**/*.xml \ 58 | -Dsonar.php.tests.reportPath=tests.xml \ 59 | -Dsonar.php.coverage.reportPaths=coverage.xml \ 60 | -Dsonar.host.url=https://sonarcloud.io \ 61 | -Dsonar.login=$SONAR_TOKEN 62 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 'on': 3 | push: 4 | branches: 5 | - "**" 6 | pull_request: 7 | branches: 8 | - "**" 9 | schedule: 10 | - cron: 0 16 * * * 11 | workflow_dispatch: 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | include: 17 | - php: '7.0' 18 | phpunit: 6 19 | composerversion: '2.2.9' 20 | - php: 7.1 21 | phpunit: 7 22 | composerversion: '2.2.9' 23 | - php: 7.2 24 | phpunit: 8 25 | composerversion: '2.2.9' 26 | - php: 7.3 27 | phpunit: 9 28 | composerversion: 'latest' 29 | - php: 7.4 30 | phpunit: 9 31 | composerversion: 'latest' 32 | - php: '8.0' 33 | phpunit: 9 34 | composerversion: 'latest' 35 | 36 | runs-on: ubuntu-latest 37 | 38 | steps: 39 | - uses: actions/checkout@v2 40 | 41 | - name: Install PHP 42 | uses: shivammathur/setup-php@v2 43 | with: 44 | php-version: ${{ matrix.php }} 45 | coverage: none 46 | tools: composer:${{ matrix.composerversion }} 47 | 48 | - name: Cache Composer dependencies 49 | uses: actions/cache@v2 50 | with: 51 | path: /tmp/composer-cache 52 | key: php${{ matrix.php }}-${{ hashFiles('**/composer.json') }} 53 | 54 | - name: Install dependencies 55 | run: | 56 | composer --no-plugins --no-scripts install 57 | composer --no-plugins --no-scripts dump-autoload -o 58 | vendor/bin/phpunit --configuration ./phpunit.xml --teamcity 59 | 60 | - name: Run tests 61 | uses: php-actions/phpunit@v3 62 | with: 63 | php_version: ${{ matrix.php }} 64 | version: ${{ matrix.phpunit }} 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | tests.xml 4 | coverage.xml 5 | .scannerwork 6 | .phpunit.result.cache 7 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 - 2021 Mastercard 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mastercard/client-encryption", 3 | "description": "Library for Mastercard API compliant payload encryption/decryption.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "openapi", 8 | "mastercard", 9 | "field-level-encryption", 10 | "fle", 11 | "encryption", 12 | "decryption", 13 | "php" 14 | ], 15 | "authors": [ 16 | { 17 | "name": "Mastercard" 18 | } 19 | ], 20 | "require": { 21 | "phpseclib/phpseclib": "~3.0", 22 | "symfony/polyfill-php70": "^1.19", 23 | "galbar/jsonpath": "^2.0" 24 | }, 25 | "require-dev": { 26 | "guzzlehttp/guzzle": "^6.2", 27 | "yoast/phpunit-polyfills": "^1.0", 28 | "phake/phake": "*" 29 | }, 30 | "suggest": { 31 | "psr/http-message": "Allow usage of the PsrHttpMessageEncryptionInterceptor class" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Mastercard\\": "src/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Mastercard\\": ["src/", "tests/"] 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | . 15 | 16 | vendor 17 | tests 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/Certificates/test_certificate-1024.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICZjCCAc+gAwIBAgIUBq7cHZY6mebdaqpq+GoZfipDAhYwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTAzMjgxOTUzMDRaFw0yOTAz 5 | MjUxOTUzMDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEB 7 | BQADgY0AMIGJAoGBAOKHxqTEIJrfQ+/l6mQFa3grOkZpep7IH/J1uPcervKW8ecn 8 | Ctv1wngxuwrOjHYlRu1iD8BVcJpBMOWxd+qXr47nWm0otZaKXLMBlvn+iSk7HYOX 9 | Md7hz9yusN8ycXRQDawVubOpJRWk1js2MTu2q5GFi54lNPZGkAYYA0xF4yGPAgMB 10 | AAGjUzBRMB0GA1UdDgQWBBS164KE51INf9Utho+9oZokMMqdEDAfBgNVHSMEGDAW 11 | gBS164KE51INf9Utho+9oZokMMqdEDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 12 | DQEBCwUAA4GBANNwxvtZVZjRStZ3SsoeHxAUZyqL+ZAWzgWOXv2pwIeJUR/rKlKJ 13 | 71894OktqM8YH/eA3eSXPcs8tF5r8mUL+F0yF+ipmkb379oQnUO6dka1KL68+SZE 14 | 0p9NWB1A8fqbMdJC0s5Ba1g1k1WFoGX/jKULasrrgGWORnxYoykwF2hq 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /resources/Certificates/test_certificate-2048.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Certificates/test_certificate-2048.der -------------------------------------------------------------------------------- /resources/Certificates/test_certificate-2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDITCCAgmgAwIBAgIJANLIazc8xI4iMA0GCSqGSIb3DQEBBQUAMCcxJTAjBgNV 3 | BAMMHHd3dy5qZWFuLWFsZXhpcy1hdWZhdXZyZS5jb20wHhcNMTkwMjIxMDg1MTM1 4 | WhcNMjkwMjE4MDg1MTM1WjAnMSUwIwYDVQQDDBx3d3cuamVhbi1hbGV4aXMtYXVm 5 | YXV2cmUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9Mp6gEFp 6 | 9E+/1SS5XrUyYKMbE7eU0dyJCfmJPz8YOkOYV7ohqwXQvjlaP/YazZ6bbmYfa2WC 7 | raOpW0o2BYijHgQ7z2a2Az87rKdAtCpZSKFW82Ijnsw++lx7EABI3tFF282ZV7LT 8 | 13n9m4th5Kldukk9euy+TuJqCvPu4xzE/NE+l4LFMr8rfD47EPQkrun5w/TXwkmJ 9 | rdnG9ejl3BLQO06Ns6Bs516geiYZ7RYxtI8Xnu0ZC0fpqDqjCPZBTORkiFeLocEP 10 | RbTgo1H+0xQFNdsMH1/0F1BI+hvdxlbc3+kHZFZFoeBMkR3jC8jDXOXNCMNWb13T 11 | in6HqPReO0KW8wIDAQABo1AwTjAdBgNVHQ4EFgQUDtqNZacrC6wR53kCpw/BfG2C 12 | t3AwHwYDVR0jBBgwFoAUDtqNZacrC6wR53kCpw/BfG2Ct3AwDAYDVR0TBAUwAwEB 13 | /zANBgkqhkiG9w0BAQUFAAOCAQEAJ09tz2BDzSgNOArYtF4lgRtjViKpV7gHVqtc 14 | 3xQT9ujbaxEgaZFPbf7/zYfWZfJggX9T54NTGqo5AXM0l/fz9AZ0bOm03rnF2I/F 15 | /ewhSlHYzvKiPM+YaswaRo1M1UPPgKpLlRDMO0u5LYiU5ICgCNm13TWgjBlzLpP6 16 | U4z2iBNq/RWBgYxypi/8NMYZ1RcCrAVSt3QnW6Gp+vW/HrE7KIlAp1gFdme3Xcx1 17 | vDRpA+MeeEyrnc4UNIqT/4bHGkKlIMKdcjZgrFfEJVFav3eJ4CZ7ZSV6Bx+9yRCL 18 | DPGlRJLISxgwsOTuUmLOxjotRxO8TdR5e1V+skEtfEctMuSVYA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /resources/Certificates/test_certificate-4096.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFazCCA1OgAwIBAgIUetKO9222ttJE5WITqB4iamgN3YQwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTAzMjgxOTUzMzlaFw0yOTAz 5 | MjUxOTUzMzlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB 7 | AQUAA4ICDwAwggIKAoICAQDKA3/w5TAMptMi3nOC4E/iwEXUYJakqxXo23aWV8w2 8 | q6B3/3EzYEjhbIg01LNAMDr1Cn5x7aYCSrE7vaXsGEF0aK0cNGtThAC6f+Wz4QKt 9 | Znm6D2IVkPZ2ysvGPTyAtGXJvAUJSO4ssrlBSMHgd9uADGSBh9UGCW4iADobstyv 10 | 2tCvnsXnchnmEz2mlxs15v0eVbnAdX60db/WX1lw5LlMk8/mm1Xdoed4ylUoHxMk 11 | gyNKJZm37puzVWgIHaNrFpU0LaOSDMid7Oyoa6v4btmrmih9xEMrIO3yKZd/vWLc 12 | Fwzt3t866v+vmAHSTopz5QZLNArahU/nhP/Rfnb7Z4pXw2oYXcum7k+jLk2rSsgP 13 | cvUNvzU/upSushK4yQs/6489sWTgDpm5zM0BvFcBGNLA4WGyFQcRUohbsVZ5ZYIJ 14 | jlOLbV5Yb1F5yXlqEDtocuiBMWrKECpXmHiXsP+JyjGvZMA6SWUUYsJFndp3jcRP 15 | uBc+22Kq7dI+y1QVu6oNua3dinhm6gMDf4oR5Jdg9oif4n0OubgHqAxOtOH+LR5F 16 | 0pz1UViYiBUkZEfhfooIA7d6uHMBS4VEoQsmXWDqLTFmc8sgJKS0b50FaSIVKqQs 17 | izE9z0rij1kQ6aEAuKHyAsSK1chzN7tNPiiGEmy4D1PgZUwFFpCIAej18rY8XuH5 18 | ZwIDAQABo1MwUTAdBgNVHQ4EFgQUoxxQtyT0d8XxMArt9/y5ct6Q8AYwHwYDVR0j 19 | BBgwFoAUoxxQtyT0d8XxMArt9/y5ct6Q8AYwDwYDVR0TAQH/BAUwAwEB/zANBgkq 20 | hkiG9w0BAQsFAAOCAgEAqrsUftFCA5rcW4ABeppq9e4DRZPXghB5GbO5uL1o6Usm 21 | zonNKU9Caoy+DfZPkoplxbwS25XqkZgRF67JqqsNDuE+Vzy2bIzhS4xYiVsrQa91 22 | S9A5oki2JmDkkkUZvLjCY53tQZvlsXar4OPUv9P+cD+rztMGgQkyu6wvTlt/B930 23 | eInOEkUMWuxUNlv69s2dBMZP6uGu2H/XLo/V3BiB1Bzbuqc54AsLsX/HLgtm0Skk 24 | 7TOUCBON1dGen3vfNaysco+bTrBweJaWwRhahlu1Km/N4OsmbhzcNVqAg2hXsIXK 25 | w94poeHfZzEA72hi415pP7067CRV9txcPwQYL/ij5ShkyOU6FJ0mcZM+LMymMK66 26 | 03n4avhW68mbKnAWfIr5BjKS1ddjFNieeJD+ugdF40iBnjxHZthozQ93CIX5tPwm 27 | X2m/+iqrYKF5sG4IjYeOcYaYRC/9522ziCCErf2FvtxrOl/yd/Hly6MTXxEF/j5g 28 | eiGjGvcZtMHtpNzozj4MJ151THrVjtp0+4F+mC7aoxLHAaDy3PBPk8EEY9x2kPLC 29 | 4AOH0jKAmEPMiMzDp8HxrtJ+fhkovSvFtCTOIRb1VvU19hWYu4jGpOaF6lRUTrAp 30 | FGtlAbBEURtfPucs3dZa3/iVvq5aDD2nOHsAl3nyymtOLtYDCXN6zx9+ROyWgMo= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /resources/Certificates/test_certificate-512.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB4TCCAYugAwIBAgIUcINgyQ60WIgYlx6fqrRJlOplzb8wDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xOTAzMjgxOTUyMjRaFw0yOTAz 5 | MjUxOTUyMjRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF 7 | AANLADBIAkEA2CRwNbQsHDbCGImV33MyrZ+zno11lw/FoziRs3k0kpxYy//XbSY5 8 | StGWqzF03SyWyq+fLEb4P7B4d0RBUVxSfwIDAQABo1MwUTAdBgNVHQ4EFgQUuz7A 9 | dJlXdmbv8xITrsbtg/xU4UQwHwYDVR0jBBgwFoAUuz7AdJlXdmbv8xITrsbtg/xU 10 | 4UQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAANBABvhfdhBC/8Suhhu 11 | 2kB4kj0FU/8J6oG0MDxT8mT/yFKw1TdQcFGRAwu+Rfc74GsSPQ3JdgjLh8FqSzud 12 | p8kMX50= 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs1/test_key_pkcs1-1024.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDHul8A7MM3ynx1XRxmLCRZ1Lr66QppWP5ZaEotsQPpFtyq3w/x 3 | dLWZd5XXQcIb/wUQWcbxBWYJPMk/GVQ3pvkIKEqC+h08VdXtfbBlM+RE8OYaQW5O 4 | Fg7YjHgL/WvCka6VJ990RuX9Zdj9TUu8GyqERll/XUDACs85atbSW6PBQQIDAQAB 5 | AoGBAL9acs0LCZn5OLalB6FoJ0edhasA/MWjysRUI8WU898s1SwsXDUEkTxAk2HR 6 | kayK7woUSYL/nhu5jkIS/Vn4clvH7XRkch22cjAAs4oGZXUUlVrcXFDiR0o2OqAI 7 | Xh24ESM/tpDWmn/N30afn31TBAKxgY5Ej2SrmK5gjSeMGI9xAkEA+gPM/P+jLiwy 8 | wuiS4QRYxeDFtluaafl7UHR/I6vL9VaqOptwV1e/JlytKNMCwoMqadd739/BAX9d 9 | qNgpniHC9QJBAMyCZBAc8PIjfM1h4seEDeb2mxl1Y2DmcDUVWIt+E4Zs3m1I59we 10 | VgY/BwX4UIElhgsVjNo6qXYPbWiql4/tzZ0CQQCLJ6RnyO2NXIJgY8yku6Ohd6r0 11 | BeZbR8XwEPdW5l8eTb9v4WZU5vz4oCqtB02I8DKiOJK1F7g4WijKOo5neoklAkBt 12 | G9/w7M/sD9zk4qWQVrboE4faRFPZ/fe9in7sJT6biHf/DFePi6vPt06y87FXxcJH 13 | JZ85SvTgZQi1P9aO1ovNAkAVxgJq94CKCZjOiks5xTro6V0pYdsx0tuuIlx/vGVG 14 | H3bcmpSm/S54nSovzbEDN7gKr4CSSsAQqC6oPwJSQN6m 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs1/test_key_pkcs1-2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpQIBAAKCAQEAzCT8ABttdAfW851VVNMSkpgcWZ1yFTgxNu+coPpL8Ug1OZgR 3 | YOgg+fcj/A8MTYs/yXgIJhI1goXVF/9NZPfryrBPxeFIOJe+CGePGMgXv3F7o9uV 4 | 010zcyImgKntv3NrLCVw9trzIIaKNgvwgmPDhegHsWpVMr7Lk2DL5QmPE28J8ekJ 5 | nYfHJzGUSlJYJpCoCVtO7bPPDCcWgrlOzoagTujtpyBRWAPRJoA1J2b7uvzrqmuU 6 | Gf8Yqg8MIeN7Uuxs4jSk3rsDXMCE5cCVKa+bfoTV1XRQJ2PABdNuvwH/wqosRg3Y 7 | TgFdWM46AY4DgIzzqY/xSeLibd1Hzg2wHQTM6wIDAQABAoIBAQDLmjtnk/NXHRaK 8 | RCm9/wHwCRuFWV1VwoR7KQGLH/er/ntvJLZ4cyuogo92Lj/z+uS0eC2QYurRcc81 9 | LuCuygF2VuBJGEXig5z5Xue+LJpaysEojLHia3sL4kyKWHCRWHjUP8dpvLdtgiHI 10 | g6HtObjhDajWjpnIkbgSFiFlHmJ/WqA7IjEOehGiqTjrfyXpL8rbcGt+chJb2z0s 11 | RdlABjl1MT2s9cCHZLwz6x1eDQDDYyw2pRRmEddMZ5VWtAd37I8RWl2NHrMsggca 12 | JzIA5LnddsRqmMVw7+1qFIIK0ZHOTknvvgQ8+U7P+r8v7+3mufvX4JakPingj543 13 | slbOGapBAoGBAO1F4REeKfCpGMo7kWZsAASAkEb+5Fcu4jrEzZkf2jk5WC4zkWlm 14 | SAqay1WLIEGP25LCo0o8vTEfx0tONukJMmEJewVi551Nxz+clcrbiJRcX6P8RTCe 15 | cJtQjUOqwHDvKNNpcAE8zz0YOLotJxhCH5ST3aE07sc159K9EGMzedrhAoGBANxB 16 | vbghMxN7lTuSvxMOwNQOWgKfWzV+fTXLQ7SgFhICqEV67nYHm0r/j9lN2Vtxuh6L 17 | hqZ2r1khzEOhyHz7YBINAYUqjjoFAVUfsHZ7auM7sdQBZx1VwS2XRPglgpso9wEh 18 | TEz75C7LH4/2nu1BIVAduE2cc95wKPEUexps/E1LAoGBAIppxFSvCvpIOpzmyPg9 19 | snjt4rx3vw6Y3AI6glF8Qlo1eJpjHMWmlAoTqOA7K9LzL7zabFVHP3qjtifY9bFV 20 | 2xy+YhSPUNvz3nLeToerL26UwHoyFM667qe8AtxhhKec7Gz/ygX+ykoykg0RgAfn 21 | svKCm7yJ2208pgLKpf+orMIhAoGBALrpMxWRXuW2pzKR2oJSr8KEl0/Iab9gouLG 22 | pqMegvwvsxqbMseItvkTHMB8tupJ/Xa0UsTqzOznqI7wONIPBDztOpAGSAHmg3X4 23 | WWiCXXeODd9qfVXAkxmcWBP4yPfg8JPN7REbZU1sZFFoKQAPmDSDtAZwsUdfiO7k 24 | wX7wY783AoGAQ646bcqPKXmNCc2oo4O4VgcC2afzNuxoUfLgFehVREj/tbhtOpN4 25 | NwhAsQhNz4uh1UmlulKTTGZ67VWikAiQ8ip5HSBMRVT/A4ZCUh5ondU1yVxH6Q0+ 26 | eyQ+FF+jTgnAdMp0smLw7yem6HcekksgdNwhDKifFTI13mKWUh8gBew= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs1/test_key_pkcs1-4096.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKAIBAAKCAgEA0054bLCGElQG5YldXGiu/nvEnJKevV/81xI+dAMSMzS2vd2O 3 | xxD4wZtqLPTB/x+33NsrymeboScrQXn0g0G9BdVXqXqLZIUiO0I9dHvb82GCZTaA 4 | v19AXY25T+In42aVARqMNO5uVlLWVak74XMmD8WKZP3eU9wdKMXq4ehNehiIa121 5 | HNNiPtTVVvZ3TAw1VxUeEaxtDLvpqD6WO9YBKfWc8GXdrtqFVWbbYUl7izlzj943 6 | z9VwkJgsj0b6qt58AcyiOAZ56VoFUx5N5p00EDcamTYRTvJFAFQH+/ejwcTsDm2s 7 | 4xCEjzZ/7DZ9FRwovPNZIH30+JtU+ld+BhZ/mWSddI5Zh1cor0uMeGNnpg4eLJI6 8 | fJ+/JjOdkhoZacY65DHozCsy5aOccmU9BrncQGG4+H1lN8L+gOpHZ7e/X6hnHxtg 9 | NbnIoSd7Uo5yIvUiCxxfPyCP/yjOqnfDtdR8F6GkMnjzCXGngyIvv25lz4p+gPdD 10 | j6PFSVHJkRMS5Lry+c7AOYFHzJ81K8BANPbiwT8D9x1znM6GVGfH687U/sbRiWLp 11 | maqGvEBlXhGgmQ5xqhwQCB9Ds9DWzdGrbXduCoLlDIkhM8tDXdGYMDSNrrOiD4JO 12 | ZP1Bhe/6+zMNPlusd3z44eCSEXpLCixdiPNvl1vNolytnJi35VZrIXPRessCAwEA 13 | AQKCAgBmQAWUCsOF4PVJY3QzAFEVwgx8+5Im72jpJeHkv4uyDaMUMz8g4vyMq0jw 14 | oiux6cZN8By7n/E2RT7wOzRvw4LVbMwzraIALVBIPqCAWmMv3ZJ8qagZct0xqB/x 15 | IO3OY1hdJVyNTIdF7GXdI7xfNxpG7X8vqY1JJS1TCprDYGcFWxPAaKL4ZO2Ym+L0 16 | ZuWJfirdjdF0GezXCaNij46hO8hqZnjf91sTfpign9outKE82Lsr9gsp3g3PWmPN 17 | nTo1Lt3w/PXOiIu7uJz1AKgPnSiRZCjR1NEBU8jCBOesLMQoQsM7pCTR569NocC7 18 | LA7RBURNUrBhQbImDvxK+8V26rIpRd8NbY4UuTY5wFVR0qgGtRZD9ocg1Fij5nEa 19 | xnv58hIHm8x8nOAQjtLjDj/PYRjgafaaxc+9q74bz30tqf5uHoNzJVVWJdzUN1he 20 | JOtdxjFOArtkVFSJs1mr3o0wwrbRDrVguAXA3z6YZGBHIfgB1hz13TFMcxfRWVVO 21 | cpZUmwx2uXTmOOX+iBUlcPulSnUxmV9wB219F2qPZqSQG6lD05L/GUGjeB56srVh 22 | NwYgC7HO6lJkBAb9JDvYfxR9kKQ5Ppen/vma4kfTcLlbciME3S99meRfOtmlGFGd 23 | yaSjiPEYyYlQlypSVPf64hDMUu1CuyfIM9CBJ6BOq7P73+qf+QKCAQEA7KkScU+F 24 | wK0QDzT3ohlxynMCUPI52/dl2G/rRERVsB3T+W+W1YrxurOu+5hjSH+0b0dnbs3t 25 | 5UKfBq6n0CRNGPXaha8BDq8r4K0rpvRUn3Xprk41c2qV0xo3INbuCZd1ldpFu3Jj 26 | GBmIJNC2r58Za7UJYaB10WNdJrPPLa7PUKreZeSBeAqFBqHmy1jA2nQiF5GStLY/ 27 | OIa8cxy7a0ZCa+bWVEXk6tPtgnJNFU/U2IQ46P2ldNswCbwJMouG4oWCQQ7ODpQA 28 | JqeC2o9mBDsmSyULJr7NyteVCACGBmz6QVp6mxoAa80jd9KyiSQZmZTDFAvuOpkS 29 | occ/K7BCzONEZQKCAQEA5JL+03qx1IUK9RYPChZjfrn8Tv8dKOIfNto842uRmH21 30 | boR1O5fHvyGHh8+N6Ao0PeFKb7CRQSnXE0W5SLEVw/kYyzEyo/zjpir7CVncsOG8 31 | 3RtzYOtnJ/jD9wIY3UsjXqX/x6EsolsWPgzJ19Tx0qAi+fnJAX+69z8vzXlmu8OP 32 | YuuG+UMpNftJO+ZxOncxsiNw3781v9k6GKffjuMe5W655om1AlQWaECUAWX66zPW 33 | LyVq2xOj41FU4k+xMqUMREBXyeX9vpig7ZVL7rTIMdG24CyromtcusBDTxueccWD 34 | nRW7S0UPFHYApig+5QSr1Y9zN9iFWJzCIS7fnx7XbwKCAQEA6ChLYUCjcwnSsThC 35 | nI/dYr5DzWhxfelJzXKtFoD6lhQMt6rSCpWM4JwX0dQBwUMVm/wt6TK2ZqpeGk4H 36 | bVXPE+dKAM5WeTM6FeOK6PLSeMNRA57RLHGonDghUGPHiz07Kk+/DE0ADMovFf5w 37 | 2AN5CoHDvDOOoGObI7ZMTQIpeXbFSKtKnpmjOYhlQaHFPgei0gAKLKCDkE4MW9gZ 38 | uvhnfDYslushz4MqgUbjez6fC+9ZbKY2Q1Yp38LIOv9IyLoztuJxHTfulfzJjuIR 39 | L6FexWSHdfDDLHMjTYBF+dO6A5Zgo/pz40yPuKHGZmY1fsXCQM4bWvyCnJU60P7N 40 | 6PQhSQKCAQANeDQYFkTgdy6cHr6oI4WddCxQI2x+ekTIoLex1ybvS4kjiB64cktN 41 | EhbAhBSitec6NkqCpm8I3gRUmGlAxV64+7bgUnfffgmUQzgj5u3AZq0QgoucDIM5 42 | sckqhy8b60+cRj/6bZ8JukBnS62hUGUnulQVUwjrU7Ga3FhezWambfHHLIX5rmGB 43 | UtuP8hZ+EYQWMUx3gvcR5SUtSsc7zlqFvq6pzTejeX0Qi62tH2tX7OgUQyo22sNv 44 | o91SsMuKZnuAkiIaPblkP+5L0d51pKWfefJC5579pUIDp0zQHpqJrdABs8QjvWAU 45 | HpgPMpPyPwI5RYjOo63H+QTfm7mF0PV1AoIBAA4RW+P999ANeuI9Uj8GDfoTv3p0 46 | 7xy97W2vE+sJHUnK2I1klgvyumN7WX9kzIMXk+oxxhoI1OZJuTCQ87Ax9l4uhOla 47 | U3EAx0CnDGU8HyuHc/RS0ZJpX4jw7I5ay367HnNpoKexCBVvKUTt6TFcDBtZMrWf 48 | uwpxKb4ufCwShQYqI4cWTnLK4drbB+u4ZZ0l+y30w1DLYZ8wpnmjfd2lsuRHy5Zr 49 | oGnMj7kunF9JWufwd4+CLhJLS+tqUki0q+HISDPijnPYuJlfmYUaCjDNJisj/lNg 50 | 9fYp8F0HlfLCjzVi8QOzboMF8SjLZI9gfRoY5xMg9uP/Z47M+HGeFftAuiU= 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs1/test_key_pkcs1-512.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOQIBAAJBAOiY+DyTnjyIs41Z9SfqIEV+0BUuDqC4iceBIY98yTTGtlIkb27M 3 | DJjBAW2fhrxyG8Cb74SspmcD4u/mxrm6460CAwEAAQJAbbwViUa/paF8zFg/gAhG 4 | F2Nfuk5TWmIVpoj2k2J07q9W9JeY5sJfLvFwwsMc/vY6FPHGSO0P4sQ3MQHo1zyA 5 | RQIhAPir1/laHualWd5ssAJrvzoK7EtVSnjbuU1tVKBTLwnvAiEA73Paq1U0wO12 6 | DyfRcE+UQ1NwvROLVn0XQA/MAGMj+CMCIDjwMA2aQwUQy1kQjeSgAzMpGR3Os7Sk 7 | qvM9m2jyYwzlAiBrBFJUhI5BM1+yQk9+bHKM7HvUZSm/C8UaYnUAL07iFQIgCLIh 8 | fJ5xPiUD6tO1jn0Kauf1DOlwhDQC10zpOitM8xo= 9 | -----END RSA PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs12/test_key.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs12/test_key.p12 -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_invalid_key.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs8/test_invalid_key.der -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-1024.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs8/test_key_pkcs8-1024.der -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-1024.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALCcqlioDdtVqSIW 3 | /Y0PUM0YX5Us76B6VSS5e9LwFeK9g/jsrfGs72WdRxEX1uHTzZXHGimMmmODqv4M 4 | bZ5Qbbs7dCZwSKlqNlSKLx40zwy4xZh1ie/UJ2CgUhu62wXDnUMg4HE0l9oY5g/P 5 | BXvSbuGM87gMnNOk0E+pfWHjQbvlAgMBAAECgYBggfuD3rFTvYdinXWH82qP6FWy 6 | yo9W/gIwwzqqlY8gC7dl+s9CVOGsgTkoWgKN/JNG2Tmuoqpq3rQ9hsUP0Ztj32ty 7 | VfEWqKipDtC0x2xi2D7I25gxVLu521tqLhoaWN1oWdPraflXEkEY9p8CD8tzxc6Q 8 | wi6ShZ8TSacuLvivgQJBAOd/X4iNmmPZ0AY+ZVri4Ic+YXOOapagkXuR2Y5pcRfw 9 | 3jueEwZMIo142L+dFZMDORsqPhFwwCzOcbn2NEID4ckCQQDDTh607HbkvXAwYHDZ 10 | 6gpczrhSAkpjQ0fffOFuF8sI5A64KjnTGte+fm8ic5Mess1oP6LdaS0HvJs4n+m7 11 | nPc9AkEApTwbOmKoQoEjpHFA8wBhducls8+BcQYnEWZnPOkyGf6JAVCxD5ukRgpt 12 | 20cKMSbpyeP67YPnB5RLRIrhfgU7UQJAX3d5NRD9UPR0uYD6yNpJNHJr0NKD0B+c 13 | K1dczjbdLTxlIYqqd1GAsgIViu6ZtIDMPTAWCUqXE1gTO8uXMfkZNQJASgdEiTiI 14 | EnZLto+1hRr/fYNzIJHDelJ2oGtzbRmUtXQ8d489obsZusK2kSPWgX1lzXjvrv07 15 | jznkRk0QtDG8Vw== 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-2048.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs8/test_key_pkcs8-2048.der -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-2048.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD0ynqAQWn0T7/V 3 | JLletTJgoxsTt5TR3IkJ+Yk/Pxg6Q5hXuiGrBdC+OVo/9hrNnptuZh9rZYKto6lb 4 | SjYFiKMeBDvPZrYDPzusp0C0KllIoVbzYiOezD76XHsQAEje0UXbzZlXstPXef2b 5 | i2HkqV26ST167L5O4moK8+7jHMT80T6XgsUyvyt8PjsQ9CSu6fnD9NfCSYmt2cb1 6 | 6OXcEtA7To2zoGznXqB6JhntFjG0jxee7RkLR+moOqMI9kFM5GSIV4uhwQ9FtOCj 7 | Uf7TFAU12wwfX/QXUEj6G93GVtzf6QdkVkWh4EyRHeMLyMNc5c0Iw1ZvXdOKfoeo 8 | 9F47QpbzAgMBAAECggEAK3dMmzuCSdxjTsCPnc6E3H35z914Mm97ceb6RN26OpZI 9 | FcO6OLj2oOBkMxlLFxnDta2yhIpo0tZNuyUJRKBHfov35tLxHNB8kyK7rYIbincD 10 | joHtm0PfJuuG+odiaRY11lrCkLzzOr6xlo4AWu7r8qkQnqQtAqrXc4xu7artG4rf 11 | MIunGnjjWQGzovtey1JgZctO97MU4Wvw18vgYBI6JM4eHJkZxgEhVQblBTKZs4Of 12 | iWk6MRHchgvqnWugwl213FgCzwy9cnyxTP13i9QKaFzL29TYmmN6bRWBH95z41M8 13 | IAa0CGahrSJjudZCFwsFh413YWv/pdqdkKHg1sqseQKBgQD641RYQkMn4G9vOiwB 14 | /is5M0OAhhUdWH1QtB8vvhY5ISTjFMqgIIVQvGmqDDk8QqFMOfFFqLtnArGn8HrK 15 | mBXMpRigS4ae/QgHEz34/RFjNDQ9zxIf/yoCRH5PmnPPU6x8j3bj/vJMRQA6/yng 16 | oca+9qvi3R32AtC5DUELnwyzNwKBgQD5x1iEV+albyCNNyLoT/f6LSH1NVcO+0IO 17 | vIaAVMtfy+hEEXz7izv3/AgcogVZzRARSK0qsQ+4WQN6Q2WG5cQYSyB92PR+Vgwh 18 | nagVvA+QHNDL988xoMhB5r2D2IVSRuTB2EOg7LiWHUHIExaxVkbADODDj7YV2aQC 19 | JVv0gbDQJQKBgQCaABix5Fqci6NbPvXsczvM7K6uoZ8sWDjz5NyPzbqObs3ZpdWK 20 | 3Ot4V270tnQbjTq9M4PqIlyGKp0qXO7ClQAskdq/6hxEU0UuMp2DzLNzlYPLvON/ 21 | SH1czvZJnqEfzli+TMHJyaCpOGGf1Si7fhIk/f0cUGYnsCq2rHAU1hhRmQKBgE/B 22 | JTRs1MqyJxSwLEc9cZLCYntnYrr342nNLK1BZgbalvlVFDFFjgpqwTRTT54S6jR6 23 | nkBpdPmKAqBBcOOX7ftL0b4dTkQguZLqQkdeWyHK8aiPIetYyVixkoXM1xUkadqz 24 | cTSrIW1dPiniXnaVc9XSxtnqw1tKuSGuSCRUXN65AoGBAN/AmT1S4PAQpSWufC8N 25 | UJey8S0bURUNNjd52MQ7pWzGq2QC00+dBLkTPj3KOGYpXw9ScZPbxOthBFzHOxER 26 | Wo16AFw3OeRtn4VB1QJ9XvoA/oz4lEhJKbwUfuFGGvSpYvg3vZcOHF2zlvcUu7C0 27 | ub/WhOjV9jZvU5B2Ev8x1neb 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-4096.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs8/test_key_pkcs8-4096.der -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-4096.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 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 51 | FRc4BkVecBOq/WYXqKWZ/tFJ/Ib/uG0= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-512.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Mastercard/client-encryption-php/b00c22f9fb4e28be3a67d993fb0d43b949b44761/resources/Keys/Pkcs8/test_key_pkcs8-512.der -------------------------------------------------------------------------------- /resources/Keys/Pkcs8/test_key_pkcs8-512.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEArnaGWuy6JFTryzOL 3 | 5Sj6DxTV9dVwPcPWV3p9ihr33ZGcTR2juFzWGxTQFKMRJg63rr7+ISphxDAepFRf 4 | LDbOKwIDAQABAkADPrsjB9IyiM3V+sB4Y7m6/BU6vFyZGYJsICAjqHrByUKsSXfj 5 | YTf0O8gs/9BNmvx/pl91fWfQS/NqAj1XdVdJAiEA6GJHQUlSvwgGfQrf0rVLWWOA 6 | aBkR+GFwop19oD+6JTUCIQDAMV7aYV3QNOw3krCzHfWJ146TilQzRCLhDJTwjJRx 7 | 3wIgRY/1yINMc8bROmkg6xA+B/oTHBY1HOb+Mo92ZZvt+ukCIFoozu5zLqc1rHqF 8 | fg8Ixt7bGC9ufQFvvU0FsfkGebzRAiBhuLkH4fTAIyDjtTAm0DbJ+80eub5P/qm0 9 | fjESCk2mng== 10 | -----END PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /src/Developer/Encryption/AES/AESCBC.php: -------------------------------------------------------------------------------- 1 | setKey($key); 24 | $aes->setIV($iv); 25 | $encryptedBytes = $aes->encrypt($bytes); 26 | if (false === $encryptedBytes) { 27 | throw new EncryptionException('Failed to encrypt bytes!'); 28 | } 29 | return $encryptedBytes; 30 | } 31 | 32 | /** 33 | * @param string $iv 34 | * @param string $key 35 | * @param string $encryptedBytes 36 | * @throws EncryptionException 37 | * @return string 38 | */ 39 | public static function decrypt($iv, $key, $encryptedBytes) { 40 | $aes = new AES('cbc'); 41 | $aes->setKey($key); 42 | $aes->setIV($iv); 43 | $bytes = $aes->decrypt($encryptedBytes); 44 | if (false === $bytes) { 45 | throw new EncryptionException('Failed to decrypt bytes with the provided key and IV!'); 46 | } 47 | return $bytes; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Developer/Encryption/AES/AESEncryption.php: -------------------------------------------------------------------------------- 1 | setNonce($iv); 25 | $cipher->setKey($key); 26 | $cipher->disablePadding(); 27 | $cipher->setTag($authTag); 28 | $cipher->setAAD($aad); 29 | return $cipher->decrypt($cipherText); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Developer/Encryption/EncryptionConfig.php: -------------------------------------------------------------------------------- 1 | 43 | * new HashMap<>() { 44 | * { 45 | * put("$.path.to.element.to.be.encrypted", "$.path.to.object.where.to.store.encryption.fields"); 46 | * } 47 | * } 48 | * 49 | */ 50 | private $encryptionPaths = []; 51 | 52 | /** 53 | * @var array 54 | * A list of JSON paths to decrypt in response payloads. 55 | * Example: 56 | *
 57 |      * new HashMap<>() {
 58 |      *     {
 59 |      *         put("$.path.to.object.with.encryption.fields", "$.path.where.to.write.decrypted.element");
 60 |      *     }
 61 |      * }
 62 |      * 
63 | */ 64 | private $decryptionPaths = []; 65 | 66 | /** 67 | * @var string|null 68 | * The name of the payload field where to write/read the encrypted data value. 69 | */ 70 | protected $encryptedValueFieldName = null; 71 | 72 | /** 73 | * @return string|null 74 | */ 75 | public function getEncryptionKeyFingerprint() 76 | { 77 | return $this->encryptionKeyFingerprint; 78 | } 79 | 80 | /** 81 | * @return EncryptionKey 82 | */ 83 | public function getEncryptionCertificate() 84 | { 85 | return $this->encryptionCertificate; 86 | } 87 | 88 | /** 89 | * @return DecryptionKey 90 | */ 91 | public function getDecryptionKey() 92 | { 93 | return $this->decryptionKey; 94 | } 95 | 96 | /** 97 | * @return int 98 | */ 99 | public function getScheme() 100 | { 101 | return $this->scheme; 102 | } 103 | 104 | /** 105 | * @return array 106 | */ 107 | public function getEncryptionPaths() 108 | { 109 | return $this->encryptionPaths; 110 | } 111 | 112 | /** 113 | * @return array 114 | */ 115 | public function getDecryptionPaths() 116 | { 117 | return $this->decryptionPaths; 118 | } 119 | 120 | /** 121 | * @return string 122 | */ 123 | public function getEncryptedValueFieldName() 124 | { 125 | return $this->encryptedValueFieldName; 126 | } 127 | 128 | /** 129 | * @param string|null $encryptionKeyFingerprint 130 | */ 131 | public function setEncryptionKeyFingerprint($encryptionKeyFingerprint) 132 | { 133 | $this->encryptionKeyFingerprint = $encryptionKeyFingerprint; 134 | } 135 | 136 | /** 137 | * @param string $encryptionCertificate 138 | */ 139 | public function setEncryptionCertificate($encryptionCertificate) 140 | { 141 | $this->encryptionCertificate = $encryptionCertificate; 142 | } 143 | 144 | /** 145 | * @param string|null $decryptionKey 146 | */ 147 | public function setDecryptionKey($decryptionKey) 148 | { 149 | $this->decryptionKey = $decryptionKey; 150 | } 151 | 152 | /** 153 | * @param string $encryptedValueFieldName 154 | */ 155 | public function setEncryptedValueFieldName($encryptedValueFieldName) 156 | { 157 | $this->encryptedValueFieldName = $encryptedValueFieldName; 158 | } 159 | 160 | /** 161 | * @param array $encryptionPaths 162 | */ 163 | public function setEncryptionPaths($encryptionPaths) 164 | { 165 | $this->encryptionPaths = $encryptionPaths; 166 | } 167 | 168 | /** 169 | * @param array $decryptionPaths 170 | */ 171 | public function setDecryptionPaths($decryptionPaths) 172 | { 173 | $this->decryptionPaths = $decryptionPaths; 174 | } 175 | 176 | 177 | 178 | } 179 | -------------------------------------------------------------------------------- /src/Developer/Encryption/EncryptionConfigBuilder.php: -------------------------------------------------------------------------------- 1 | encryptionCertificate == null || isset($this->encryptionKeyFingerprint)) { 45 | // No encryption certificate set or key fingerprint already provided 46 | return; 47 | } 48 | 49 | $cert = openssl_x509_read($this->encryptionCertificate->getBytes()); 50 | $this->encryptionKeyFingerprint = openssl_x509_fingerprint($cert, 'sha256'); 51 | } catch (\Exception $e) { 52 | throw new EncryptionException("Failed to compute encryption key fingerprint!", $e); 53 | } 54 | } 55 | 56 | protected function checkJsonPathParameterValues() 57 | { 58 | foreach ($this->decryptionPaths as $key => $value) { 59 | if (!JsonPath::isPathDefinite($key) || !JsonPath::isPathDefinite($value)) { 60 | throw new \InvalidArgumentException("JSON paths for decryption must point to a single item!"); 61 | } 62 | } 63 | 64 | foreach ($this->encryptionPaths as $key => $value) { 65 | if (!JsonPath::isPathDefinite($key) || !JsonPath::isPathDefinite($value)) { 66 | throw new \InvalidArgumentException("JSON paths for decryption must point to a single item!"); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Developer/Encryption/EncryptionConfigScheme.php: -------------------------------------------------------------------------------- 1 | getEncryptionPaths() as $jsonPathIn => $jsonPathOut) { 37 | $payloadJsonObject = self::encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params); 38 | } 39 | 40 | // Return the updated payload 41 | return json_encode($payloadJsonObject); 42 | } catch (\InvalidArgumentException $e) { 43 | throw $e; 44 | } catch (EncryptionException $e) { 45 | throw $e; 46 | } catch (\Exception $e) { 47 | throw new EncryptionException('Payload encryption failed!', $e); 48 | } 49 | } 50 | 51 | /** 52 | * Decrypt parts of a JSON payload using the given parameters and configuration. 53 | * @param string $payload A JSON string 54 | * @param FieldLevelEncryptionConfig $config A FieldLevelEncryptionConfig instance 55 | * @param FieldLevelEncryptionParams|null $params A FieldLevelEncryptionParams instance 56 | * @see FieldLevelEncryptionConfig 57 | * @see FieldLevelEncryptionParams 58 | * @return string The updated payload 59 | * @throws EncryptionException 60 | */ 61 | public static function decryptPayload($payload, $config, $params = null) { 62 | try { 63 | // Parse the given payload 64 | $payloadJsonObject = json_decode($payload); 65 | 66 | // Perform decryption (if needed) 67 | foreach ($config->getDecryptionPaths() as $jsonPathIn => $jsonPathOut) { 68 | $payloadJsonObject = self::decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params); 69 | } 70 | 71 | // Return the updated payload 72 | return json_encode($payloadJsonObject); 73 | } catch (\InvalidArgumentException $e) { 74 | throw $e; 75 | } catch (EncryptionException $e) { 76 | throw $e; 77 | } catch (\Exception $e) { 78 | throw new EncryptionException('Payload decryption failed!', $e); 79 | } 80 | } 81 | 82 | /** 83 | * @param \stdClass $payloadJsonObject 84 | * @param string $jsonPathIn 85 | * @param string $jsonPathOut 86 | * @param FieldLevelEncryptionConfig $config 87 | * @param FieldLevelEncryptionParams|null $params 88 | * @throws EncryptionException 89 | */ 90 | private static function encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params) { 91 | 92 | $inJsonObject = JsonUtils::readJsonElement($payloadJsonObject, $jsonPathIn); 93 | if (is_null($inJsonObject)) { 94 | // Nothing to encrypt 95 | return $payloadJsonObject; 96 | } 97 | 98 | if (empty($params)) { 99 | // Generate encryption params 100 | $params = FieldLevelEncryptionParams::generate($config); 101 | } 102 | 103 | // Encrypt data at the given JSON path 104 | $inJsonString = JsonUtils::sanitize(JsonUtils::toJsonString($inJsonObject)); 105 | $encryptedValueBytes = AESCBC::encrypt($params->getIvBytes(), $params->getSecretKeyBytes(), $inJsonString); 106 | $encryptedValue = EncodingUtils::encodeBytes($encryptedValueBytes, $config->getFieldValueEncoding()); 107 | 108 | // Delete data in clear 109 | if ('$' !== $jsonPathIn) { 110 | JsonPath::delete($payloadJsonObject, $jsonPathIn); 111 | } else { 112 | $payloadJsonObject = json_decode('{}'); 113 | } 114 | 115 | // Add encrypted data and encryption fields at the given JSON path 116 | $outJsonObject = JsonUtils::checkOrCreateOutObject($payloadJsonObject, $jsonPathOut); 117 | $outJsonObject->{$config->getEncryptedValueFieldName()} = $encryptedValue; 118 | if (!empty($config->getIvFieldName())) { 119 | $outJsonObject->{$config->getIvFieldName()} = $params->getIvValue(); 120 | } 121 | if (!empty($config->getEncryptedKeyFieldName())) { 122 | $outJsonObject->{$config->getEncryptedKeyFieldName()} = $params->getEncryptedKeyValue(); 123 | } 124 | if (!empty($config->getEncryptionCertificateFingerprintFieldName())) { 125 | $outJsonObject->{$config->getEncryptionCertificateFingerprintFieldName()} = $config->getEncryptionCertificateFingerprint(); 126 | } 127 | if (!empty($config->getEncryptionKeyFingerprintFieldName())) { 128 | $outJsonObject->{$config->getEncryptionKeyFingerprintFieldName()} = $config->getEncryptionKeyFingerprint(); 129 | } 130 | if (!empty($config->getOaepPaddingDigestAlgorithmFieldName())) { 131 | $outJsonObject->{$config->getOaepPaddingDigestAlgorithmFieldName()} = $params->getOaepPaddingDigestAlgorithmValue(); 132 | } 133 | return $payloadJsonObject; 134 | } 135 | 136 | /** 137 | * @param \stdClass $payloadJsonObject 138 | * @param string $jsonPathIn 139 | * @param string $jsonPathOut 140 | * @param FieldLevelEncryptionConfig $config 141 | * @param FieldLevelEncryptionParams|null $params 142 | * @throws EncryptionException 143 | */ 144 | private static function decryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config, $params) { 145 | 146 | $inJsonObject = JsonUtils::readJsonObject($payloadJsonObject, $jsonPathIn); 147 | if (is_null($inJsonObject)) { 148 | // Nothing to decrypt 149 | return $payloadJsonObject; 150 | } 151 | 152 | // Read and remove encrypted data and encryption fields at the given JSON path 153 | $encryptedValueJsonElement = JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getEncryptedValueFieldName()); 154 | if (empty($encryptedValueJsonElement)) { 155 | // Nothing to decrypt 156 | return $payloadJsonObject; 157 | } 158 | 159 | if (!$config->useHttpPayloads() && empty($params)) { 160 | throw new \InvalidArgumentException('Encryption params have to be set when not stored in HTTP payloads!'); 161 | } 162 | 163 | if (empty($params)) { 164 | // Read encryption params from the payload 165 | $oaepDigestAlgorithmJsonElement = JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getOaepPaddingDigestAlgorithmFieldName()); 166 | $oaepDigestAlgorithm = empty($oaepDigestAlgorithmJsonElement) ? $config->getOaepPaddingDigestAlgorithm() : $oaepDigestAlgorithmJsonElement; 167 | $encryptedKeyJsonElement = JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getEncryptedKeyFieldName()); 168 | $ivJsonElement = JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getIvFieldName()); 169 | JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getEncryptionCertificateFingerprintFieldName()); 170 | JsonUtils::readAndDeleteJsonKey($inJsonObject, $config->getEncryptionKeyFingerprintFieldName()); 171 | $params = new FieldLevelEncryptionParams($config, $ivJsonElement, $encryptedKeyJsonElement, $oaepDigestAlgorithm); 172 | } 173 | 174 | // Decrypt data 175 | $encryptedValueBytes = EncodingUtils::decodeValue($encryptedValueJsonElement, $config->getFieldValueEncoding()); 176 | $decryptedValueBytes = AESCBC::decrypt($params->getIvBytes(), $params->getSecretKeyBytes(), $encryptedValueBytes); 177 | 178 | // Add decrypted data at the given JSON path 179 | $decryptedValue = JsonUtils::sanitize($decryptedValueBytes); 180 | $outJsonObject = JsonUtils::checkOrCreateOutObject($payloadJsonObject, $jsonPathOut); 181 | $payloadJsonObject = JsonUtils::addDecryptedDataToPayload($payloadJsonObject, $jsonPathOut, $outJsonObject, $decryptedValue); 182 | 183 | // Remove the input if now empty 184 | $inJsonElement = JsonUtils::readJsonElement($payloadJsonObject, $jsonPathIn); 185 | if (empty((array)$inJsonElement) && '$' !== $jsonPathIn) { 186 | JsonPath::delete($payloadJsonObject, $jsonPathIn); 187 | } 188 | return $payloadJsonObject; 189 | } 190 | 191 | 192 | } 193 | -------------------------------------------------------------------------------- /src/Developer/Encryption/FieldLevelEncryptionConfig.php: -------------------------------------------------------------------------------- 1 | 47 | * array( 48 | * '$.path.to.element.to.be.encrypted' => '$.path.to.object.where.to.store.encryption.fields' 49 | * ) 50 | * 51 | */ 52 | private $encryptionPaths = array(); 53 | 54 | /** 55 | * A list of JSON paths to decrypt in response payloads. 56 | * Example: 57 | *
 58 |      * array(
 59 |      *     '$.path.to.object.with.encryption.fields' => '$.path.where.to.write.decrypted.element'
 60 |      * )
 61 |      * 
62 | */ 63 | private $decryptionPaths = array(); 64 | 65 | /** 66 | * The digest algorithm to be used for the RSA OAEP padding. Example: 'SHA-512'. 67 | * @var string 68 | */ 69 | private $oaepPaddingDigestAlgorithm; 70 | 71 | /** 72 | * The name of the payload field where to write/read the digest algorithm used for 73 | * the RSA OAEP padding (optional, the field won't be set if the name is null or empty). 74 | * @var string|null 75 | */ 76 | private $oaepPaddingDigestAlgorithmFieldName; 77 | 78 | /** 79 | * The name of the HTTP header where to write/read the digest algorithm used for 80 | * the RSA OAEP padding (optional, the header won't be set if the name is null or empty). 81 | * @var string|null 82 | */ 83 | private $oaepPaddingDigestAlgorithmHeaderName; 84 | 85 | /** 86 | * The name of the payload field where to write/read the initialization vector value. 87 | * @var string|null 88 | */ 89 | private $ivFieldName; 90 | 91 | /** 92 | * The name of the header where to write/read the initialization vector value. 93 | * @var string|null 94 | */ 95 | private $ivHeaderName; 96 | 97 | /** 98 | * The name of the payload field where to write/read the one-time usage encrypted symmetric key. 99 | * @var string|null 100 | */ 101 | private $encryptedKeyFieldName; 102 | 103 | /** 104 | * The name of the header where to write/read the one-time usage encrypted symmetric key. 105 | * @var string|null 106 | */ 107 | private $encryptedKeyHeaderName; 108 | 109 | /** 110 | * The name of the payload field where to write/read the encrypted data value. 111 | * @var string|null 112 | */ 113 | private $encryptedValueFieldName; 114 | 115 | /** 116 | * The name of the payload field where to write/read the digest of the encryption 117 | * certificate (optional, the field won't be set if the name is null or empty). 118 | * @var string|null 119 | */ 120 | private $encryptionCertificateFingerprintFieldName; 121 | 122 | /** 123 | * The name of the header where to write/read the digest of the encryption 124 | * certificate (optional, the header won't be set if the name is null or empty). 125 | * @var string|null 126 | */ 127 | private $encryptionCertificateFingerprintHeaderName; 128 | 129 | /** 130 | * The name of the payload field where to write/read the digest of the encryption 131 | * key (optional, the field won't be set if the name is null or empty). 132 | * @var string|null 133 | */ 134 | private $encryptionKeyFingerprintFieldName; 135 | 136 | /** 137 | * The name of the header where to write/read the digest of the encryption 138 | * key (optional, the header won't be set if the name is null or empty). 139 | * @var string|null 140 | */ 141 | private $encryptionKeyFingerprintHeaderName; 142 | 143 | /** 144 | * How the field/header values have to be encoded. 145 | * @see FieldValueEncoding 146 | * @var int 147 | */ 148 | private $fieldValueEncoding; 149 | 150 | /** 151 | * If the encryption parameters must be written to/read from HTTP headers. 152 | * @return bool 153 | */ 154 | public function useHttpHeaders() { 155 | return !empty($this->encryptedKeyHeaderName) && !empty($this->ivHeaderName); 156 | } 157 | 158 | /** 159 | * If the encryption parameters must be written to/read from HTTP payloads. 160 | * @return bool 161 | */ 162 | public function useHttpPayloads() { 163 | return !empty($this->encryptedKeyFieldName) && !empty($this->ivFieldName); 164 | } 165 | 166 | /** 167 | * FieldLevelEncryptionConfig constructor. 168 | * 169 | * @param EncryptionKey $encryptionCertificate 170 | * @param string $encryptionCertificateFingerprint 171 | * @param string $encryptionKeyFingerprint 172 | * @param DecryptionKey $decryptionKey 173 | * @param array $encryptionPaths 174 | * @param array $decryptionPaths 175 | * @param string $oaepPaddingDigestAlgorithm 176 | * @param string|null $oaepPaddingDigestAlgorithmFieldName 177 | * @param string|null $oaepPaddingDigestAlgorithmHeaderName 178 | * @param string|null $ivFieldName 179 | * @param string|null $ivHeaderName 180 | * @param string|null $encryptedKeyFieldName 181 | * @param string|null $encryptedKeyHeaderName 182 | * @param string|null $encryptedValueFieldName 183 | * @param string|null $encryptionCertificateFingerprintFieldName 184 | * @param string|null $encryptionCertificateFingerprintHeaderName 185 | * @param string|null $encryptionKeyFingerprintFieldName 186 | * @param string|null $encryptionKeyFingerprintHeaderName 187 | * @param int $fieldValueEncoding 188 | */ 189 | public function __construct($encryptionCertificate, $encryptionCertificateFingerprint, $encryptionKeyFingerprint, $decryptionKey, $encryptionPaths, $decryptionPaths, $oaepPaddingDigestAlgorithm, $oaepPaddingDigestAlgorithmFieldName, $oaepPaddingDigestAlgorithmHeaderName, $ivFieldName, $ivHeaderName, $encryptedKeyFieldName, $encryptedKeyHeaderName, $encryptedValueFieldName, $encryptionCertificateFingerprintFieldName, $encryptionCertificateFingerprintHeaderName, $encryptionKeyFingerprintFieldName, $encryptionKeyFingerprintHeaderName, $fieldValueEncoding) { 190 | $this->encryptionCertificate = $encryptionCertificate; 191 | $this->encryptionCertificateFingerprint = $encryptionCertificateFingerprint; 192 | $this->encryptionKeyFingerprint = $encryptionKeyFingerprint; 193 | $this->decryptionKey = $decryptionKey; 194 | $this->encryptionPaths = $encryptionPaths; 195 | $this->decryptionPaths = $decryptionPaths; 196 | $this->oaepPaddingDigestAlgorithm = $oaepPaddingDigestAlgorithm; 197 | $this->oaepPaddingDigestAlgorithmFieldName = $oaepPaddingDigestAlgorithmFieldName; 198 | $this->oaepPaddingDigestAlgorithmHeaderName = $oaepPaddingDigestAlgorithmHeaderName; 199 | $this->ivFieldName = $ivFieldName; 200 | $this->ivHeaderName = $ivHeaderName; 201 | $this->encryptedKeyFieldName = $encryptedKeyFieldName; 202 | $this->encryptedKeyHeaderName = $encryptedKeyHeaderName; 203 | $this->encryptedValueFieldName = $encryptedValueFieldName; 204 | $this->encryptionCertificateFingerprintFieldName = $encryptionCertificateFingerprintFieldName; 205 | $this->encryptionCertificateFingerprintHeaderName = $encryptionCertificateFingerprintHeaderName; 206 | $this->encryptionKeyFingerprintFieldName = $encryptionKeyFingerprintFieldName; 207 | $this->encryptionKeyFingerprintHeaderName = $encryptionKeyFingerprintHeaderName; 208 | $this->fieldValueEncoding = $fieldValueEncoding; 209 | } 210 | 211 | /** 212 | * @return EncryptionKey 213 | */ 214 | public function getEncryptionCertificate() { 215 | return $this->encryptionCertificate; 216 | } 217 | 218 | /** 219 | * @return string 220 | */ 221 | public function getEncryptionCertificateFingerprint() { 222 | return $this->encryptionCertificateFingerprint; 223 | } 224 | 225 | /** 226 | * @return string 227 | */ 228 | public function getEncryptionKeyFingerprint() { 229 | return $this->encryptionKeyFingerprint; 230 | } 231 | 232 | /** 233 | * @return DecryptionKey 234 | */ 235 | public function getDecryptionKey() { 236 | return $this->decryptionKey; 237 | } 238 | 239 | /** 240 | * @return array 241 | */ 242 | public function getEncryptionPaths() { 243 | return $this->encryptionPaths; 244 | } 245 | 246 | /** 247 | * @return array 248 | */ 249 | public function getDecryptionPaths() { 250 | return $this->decryptionPaths; 251 | } 252 | 253 | /** 254 | * @return string 255 | */ 256 | public function getOaepPaddingDigestAlgorithm() { 257 | return $this->oaepPaddingDigestAlgorithm; 258 | } 259 | 260 | /** 261 | * @return string|null 262 | */ 263 | public function getOaepPaddingDigestAlgorithmFieldName() { 264 | return $this->oaepPaddingDigestAlgorithmFieldName; 265 | } 266 | 267 | /** 268 | * @return string|null 269 | */ 270 | public function getOaepPaddingDigestAlgorithmHeaderName() { 271 | return $this->oaepPaddingDigestAlgorithmHeaderName; 272 | } 273 | 274 | /** 275 | * @return string|null 276 | */ 277 | public function getIvFieldName() { 278 | return $this->ivFieldName; 279 | } 280 | 281 | /** 282 | * @return string|null 283 | */ 284 | public function getIvHeaderName() { 285 | return $this->ivHeaderName; 286 | } 287 | 288 | /** 289 | * @return string|null 290 | */ 291 | public function getEncryptedKeyFieldName() { 292 | return $this->encryptedKeyFieldName; 293 | } 294 | 295 | /** 296 | * @return string|null 297 | */ 298 | public function getEncryptedKeyHeaderName() { 299 | return $this->encryptedKeyHeaderName; 300 | } 301 | 302 | /** 303 | * @return string|null 304 | */ 305 | public function getEncryptedValueFieldName() { 306 | return $this->encryptedValueFieldName; 307 | } 308 | 309 | /** 310 | * @return string|null 311 | */ 312 | public function getEncryptionCertificateFingerprintFieldName() { 313 | return $this->encryptionCertificateFingerprintFieldName; 314 | } 315 | 316 | /** 317 | * @return string|null 318 | */ 319 | public function getEncryptionCertificateFingerprintHeaderName() { 320 | return $this->encryptionCertificateFingerprintHeaderName; 321 | } 322 | 323 | /** 324 | * @return string|null 325 | */ 326 | public function getEncryptionKeyFingerprintFieldName() { 327 | return $this->encryptionKeyFingerprintFieldName; 328 | } 329 | 330 | /** 331 | * @return string|null 332 | */ 333 | public function getEncryptionKeyFingerprintHeaderName() { 334 | return $this->encryptionKeyFingerprintHeaderName; 335 | } 336 | 337 | /** 338 | * @return int 339 | */ 340 | public function getFieldValueEncoding() { 341 | return $this->fieldValueEncoding; 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /src/Developer/Encryption/FieldLevelEncryptionConfigBuilder.php: -------------------------------------------------------------------------------- 1 | encryptionCertificate = $encryptionCertificate; 63 | return $this; 64 | } 65 | 66 | /** 67 | * @param string $encryptionCertificateFingerprint 68 | * @see FieldLevelEncryptionConfig::encryptionCertificateFingerprint. 69 | * @return $this 70 | */ 71 | public function withEncryptionCertificateFingerprint($encryptionCertificateFingerprint) { 72 | $this->encryptionCertificateFingerprint = $encryptionCertificateFingerprint; 73 | return $this; 74 | } 75 | 76 | /** 77 | * @param string $encryptionKeyFingerprint 78 | * @see FieldLevelEncryptionConfig::encryptionKeyFingerprint. 79 | * @return $this 80 | */ 81 | public function withEncryptionKeyFingerprint($encryptionKeyFingerprint) { 82 | $this->encryptionKeyFingerprint = $encryptionKeyFingerprint; 83 | return $this; 84 | } 85 | 86 | /** 87 | * @param DecryptionKey $decryptionKey 88 | * @see FieldLevelEncryptionConfig::decryptionKey. 89 | * @return $this 90 | */ 91 | public function withDecryptionKey($decryptionKey) { 92 | $this->decryptionKey = $decryptionKey; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @param string $jsonPathIn 98 | * @param string $jsonPathOut 99 | * @see FieldLevelEncryptionConfig::encryptionPaths. 100 | * @return $this 101 | */ 102 | public function withEncryptionPath($jsonPathIn, $jsonPathOut) { 103 | $this->encryptionPaths[$jsonPathIn] = $jsonPathOut; 104 | return $this; 105 | } 106 | 107 | /** 108 | * @param string $jsonPathIn 109 | * @param string $jsonPathOut 110 | * @see FieldLevelEncryptionConfig::decryptionPaths. 111 | * @return $this 112 | */ 113 | public function withDecryptionPath($jsonPathIn, $jsonPathOut) { 114 | $this->decryptionPaths[$jsonPathIn] = $jsonPathOut; 115 | return $this; 116 | } 117 | 118 | /** 119 | * @param string $oaepPaddingDigestAlgorithm 120 | * @see FieldLevelEncryptionConfig::oaepPaddingDigestAlgorithm. 121 | * @return $this 122 | */ 123 | public function withOaepPaddingDigestAlgorithm($oaepPaddingDigestAlgorithm) { 124 | $this->oaepPaddingDigestAlgorithm = $oaepPaddingDigestAlgorithm; 125 | return $this; 126 | } 127 | 128 | /** 129 | * @param string|null $ivFieldName 130 | * @see FieldLevelEncryptionConfig::ivFieldName. 131 | * @return $this 132 | */ 133 | public function withIvFieldName($ivFieldName) { 134 | $this->ivFieldName = $ivFieldName; 135 | return $this; 136 | } 137 | 138 | /** 139 | * @param string|null $oaepPaddingDigestAlgorithmFieldName 140 | * @see FieldLevelEncryptionConfig::oaepPaddingDigestAlgorithmFieldName. 141 | * @return $this 142 | */ 143 | public function withOaepPaddingDigestAlgorithmFieldName($oaepPaddingDigestAlgorithmFieldName) { 144 | $this->oaepPaddingDigestAlgorithmFieldName = $oaepPaddingDigestAlgorithmFieldName; 145 | return $this; 146 | } 147 | 148 | /** 149 | * @param string|null $encryptedKeyFieldName 150 | * @see FieldLevelEncryptionConfig::encryptedKeyFieldName. 151 | * @return $this 152 | */ 153 | public function withEncryptedKeyFieldName($encryptedKeyFieldName) { 154 | $this->encryptedKeyFieldName = $encryptedKeyFieldName; 155 | return $this; 156 | } 157 | 158 | /** 159 | * @param string $encryptedValueFieldName 160 | * @see FieldLevelEncryptionConfig::encryptedValueFieldName. 161 | * @return $this 162 | */ 163 | public function withEncryptedValueFieldName($encryptedValueFieldName) { 164 | $this->encryptedValueFieldName = $encryptedValueFieldName; 165 | return $this; 166 | } 167 | 168 | /** 169 | * @param string|null $encryptionCertificateFingerprintFieldName 170 | * @see FieldLevelEncryptionConfig::encryptionCertificateFingerprintFieldName. 171 | * @return $this 172 | */ 173 | public function withEncryptionCertificateFingerprintFieldName($encryptionCertificateFingerprintFieldName) { 174 | $this->encryptionCertificateFingerprintFieldName = $encryptionCertificateFingerprintFieldName; 175 | return $this; 176 | } 177 | 178 | /** 179 | * @param string|null $encryptionKeyFingerprintFieldName 180 | * @see FieldLevelEncryptionConfig::encryptionKeyFingerprintFieldName. 181 | * @return $this 182 | */ 183 | public function withEncryptionKeyFingerprintFieldName($encryptionKeyFingerprintFieldName) { 184 | $this->encryptionKeyFingerprintFieldName = $encryptionKeyFingerprintFieldName; 185 | return $this; 186 | } 187 | 188 | /** 189 | * @param int $fieldValueEncoding 190 | * @see FieldLevelEncryptionConfig::fieldValueEncoding. 191 | * @return $this 192 | */ 193 | public function withFieldValueEncoding($fieldValueEncoding) { 194 | $this->fieldValueEncoding = $fieldValueEncoding; 195 | return $this; 196 | } 197 | 198 | /** 199 | * @param string $ivHeaderName 200 | * @see FieldLevelEncryptionConfig::ivHeaderName. 201 | * @return $this 202 | */ 203 | public function withIvHeaderName($ivHeaderName) { 204 | $this->ivHeaderName = $ivHeaderName; 205 | return $this; 206 | } 207 | 208 | /** 209 | * @param string $oaepPaddingDigestAlgorithmHeaderName 210 | * @see FieldLevelEncryptionConfig::oaepPaddingDigestAlgorithmHeaderName. 211 | * @return $this 212 | */ 213 | public function withOaepPaddingDigestAlgorithmHeaderName($oaepPaddingDigestAlgorithmHeaderName) { 214 | $this->oaepPaddingDigestAlgorithmHeaderName = $oaepPaddingDigestAlgorithmHeaderName; 215 | return $this; 216 | } 217 | 218 | /** 219 | * @param string $encryptedKeyHeaderName 220 | * @see FieldLevelEncryptionConfig::encryptedKeyHeaderName. 221 | * @return $this 222 | */ 223 | public function withEncryptedKeyHeaderName($encryptedKeyHeaderName) { 224 | $this->encryptedKeyHeaderName = $encryptedKeyHeaderName; 225 | return $this; 226 | } 227 | 228 | /** 229 | * @param string $encryptionCertificateFingerprintHeaderName 230 | * @see FieldLevelEncryptionConfig::encryptionCertificateFingerprintHeaderName. 231 | * @return $this 232 | */ 233 | public function withEncryptionCertificateFingerprintHeaderName($encryptionCertificateFingerprintHeaderName) { 234 | $this->encryptionCertificateFingerprintHeaderName = $encryptionCertificateFingerprintHeaderName; 235 | return $this; 236 | } 237 | 238 | /** 239 | * @param string $encryptionKeyFingerprintHeaderName 240 | * @see FieldLevelEncryptionConfig::encryptionKeyFingerprintHeaderName. 241 | * @return $this 242 | */ 243 | public function withEncryptionKeyFingerprintHeaderName($encryptionKeyFingerprintHeaderName) { 244 | $this->encryptionKeyFingerprintHeaderName = $encryptionKeyFingerprintHeaderName; 245 | return $this; 246 | } 247 | 248 | /** 249 | * Build a FieldLevelEncryptionConfig. 250 | * @see FieldLevelEncryptionConfig 251 | * @throws EncryptionException 252 | * @throws \InvalidArgumentException 253 | * @return FieldLevelEncryptionConfig 254 | */ 255 | public function build() { 256 | 257 | $this->checkJsonPathParameterValues(); 258 | $this->checkParameterValues(); 259 | $this->checkParameterConsistency(); 260 | 261 | $this->computeEncryptionCertificateFingerprintWhenNeeded(); 262 | $this->computeEncryptionKeyFingerprintWhenNeeded(); 263 | 264 | return new FieldLevelEncryptionConfig( 265 | $this->encryptionCertificate, 266 | $this->encryptionCertificateFingerprint, 267 | $this->encryptionKeyFingerprint, 268 | $this->decryptionKey, 269 | $this->encryptionPaths, 270 | $this->decryptionPaths, 271 | $this->oaepPaddingDigestAlgorithm, 272 | $this->oaepPaddingDigestAlgorithmFieldName, 273 | $this->oaepPaddingDigestAlgorithmHeaderName, 274 | $this->ivFieldName, 275 | $this->ivHeaderName, 276 | $this->encryptedKeyFieldName, 277 | $this->encryptedKeyHeaderName, 278 | $this->encryptedValueFieldName, 279 | $this->encryptionCertificateFingerprintFieldName, 280 | $this->encryptionCertificateFingerprintHeaderName, 281 | $this->encryptionKeyFingerprintFieldName, 282 | $this->encryptionKeyFingerprintHeaderName, 283 | $this->fieldValueEncoding 284 | ); 285 | } 286 | 287 | /** 288 | * @throws \InvalidArgumentException 289 | */ 290 | private function checkJsonPathParameterValues() { 291 | foreach ($this->decryptionPaths as $jsonPathIn => $jsonPathOut) { 292 | if (!JsonPath::isPathDefinite($jsonPathIn) || !JsonPath::isPathDefinite($jsonPathOut)) { 293 | throw new \InvalidArgumentException('JSON paths for decryption must point to a single item!'); 294 | } 295 | } 296 | foreach ($this->encryptionPaths as $jsonPathIn => $jsonPathOut) { 297 | if (!JsonPath::isPathDefinite($jsonPathIn) || !JsonPath::isPathDefinite($jsonPathOut)) { 298 | throw new \InvalidArgumentException('JSON paths for encryption must point to a single item!'); 299 | } 300 | } 301 | } 302 | 303 | /** 304 | * @throws \InvalidArgumentException 305 | */ 306 | private function checkParameterValues() { 307 | if (empty($this->oaepPaddingDigestAlgorithm)) { 308 | throw new \InvalidArgumentException('The digest algorithm for OAEP cannot be empty!'); 309 | } 310 | 311 | if ('SHA-256' !== $this->oaepPaddingDigestAlgorithm && 'SHA-512' !== $this->oaepPaddingDigestAlgorithm) { 312 | throw new \InvalidArgumentException('Unsupported OAEP digest algorithm: ' . $this->oaepPaddingDigestAlgorithm . '!'); 313 | } 314 | 315 | if (empty($this->fieldValueEncoding)) { 316 | throw new \InvalidArgumentException('Value encoding for fields and headers cannot be empty!'); 317 | } 318 | 319 | if (empty($this->ivFieldName) && empty($this->ivHeaderName)) { 320 | throw new \InvalidArgumentException('At least one of IV field name or IV header name must be set!'); 321 | } 322 | 323 | if (empty($this->encryptedKeyFieldName) && empty($this->encryptedKeyHeaderName)) { 324 | throw new \InvalidArgumentException('At least one of encrypted key field name or encrypted key header name must be set!'); 325 | } 326 | 327 | if (empty($this->encryptedValueFieldName)) { 328 | throw new \InvalidArgumentException('Encrypted value field name cannot be empty!'); 329 | } 330 | } 331 | 332 | /** 333 | * @throws \InvalidArgumentException 334 | */ 335 | private function checkParameterConsistency () { 336 | if (!empty($this->decryptionPaths) && empty($this->decryptionKey)) { 337 | throw new \InvalidArgumentException('Can\'t decrypt without decryption key!'); 338 | } 339 | 340 | if (!empty($this->encryptionPaths) && empty($this->encryptionCertificate)) { 341 | throw new \InvalidArgumentException('Can\'t encrypt without encryption key!'); 342 | } 343 | 344 | if (!empty($this->ivHeaderName) && empty($this->encryptedKeyHeaderName) 345 | || empty($this->ivHeaderName) && !empty($this->encryptedKeyHeaderName)) { 346 | throw new \InvalidArgumentException('IV header name and encrypted key header name must be both set or both unset!'); 347 | } 348 | 349 | if (!empty($this->ivFieldName) && empty($this->encryptedKeyFieldName) 350 | || empty($this->ivFieldName) && !empty($this->encryptedKeyFieldName)) { 351 | throw new \InvalidArgumentException('IV field name and encrypted key field name must be both set or both unset!'); 352 | } 353 | } 354 | 355 | /** 356 | * @throws EncryptionException 357 | */ 358 | private function computeEncryptionCertificateFingerprintWhenNeeded() { 359 | $providedEncryptionCertificate = $this->encryptionCertificate; 360 | if (empty($providedEncryptionCertificate) || !empty($this->encryptionCertificateFingerprint)) { 361 | // No encryption certificate set or certificate fingerprint already provided 362 | return; 363 | } 364 | try { 365 | $this->encryptionCertificateFingerprint = openssl_x509_fingerprint($providedEncryptionCertificate->getBytes(), 'sha256'); 366 | } catch (\Exception $e) { 367 | throw new EncryptionException('Failed to compute encryption certificate fingerprint!', $e); 368 | } 369 | } 370 | 371 | /** 372 | * @throws EncryptionException 373 | */ 374 | private function computeEncryptionKeyFingerprintWhenNeeded() { 375 | $providedEncryptionCertificate = $this->encryptionCertificate; 376 | if (empty($providedEncryptionCertificate) || !empty($this->encryptionKeyFingerprint)) { 377 | // No encryption certificate set or key fingerprint already provided 378 | return; 379 | } 380 | try { 381 | $publicKeyPem = openssl_pkey_get_details(openssl_pkey_get_public($providedEncryptionCertificate->getBytes()))['key']; 382 | $publicKeyDer = EncodingUtils::pemToDer($publicKeyPem, '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----'); 383 | $hash = new Hash('sha256'); 384 | $this->encryptionKeyFingerprint = EncodingUtils::encodeBytes($hash->hash($publicKeyDer), FieldValueEncoding::HEX); 385 | } catch (\Exception $e) { 386 | throw new EncryptionException('Failed to compute encryption key fingerprint!', $e); 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /src/Developer/Encryption/FieldLevelEncryptionParams.php: -------------------------------------------------------------------------------- 1 | ivValue = $ivValue; 33 | $this->encryptedKeyValue = $encryptedKeyValue; 34 | $this->oaepPaddingDigestAlgorithmValue = $oaepPaddingDigestAlgorithmValue; 35 | $this->config = $config; 36 | } 37 | 38 | /** 39 | * Generate encryption parameters. 40 | * @param FieldLevelEncryptionConfig $config A FieldLevelEncryptionConfig instance 41 | * @return FieldLevelEncryptionParams 42 | * @throws EncryptionException 43 | */ 44 | public static function generate($config) { 45 | // Generate a random IV 46 | $iv = AESEncryption::generateIv(); 47 | $ivValue = EncodingUtils::encodeBytes($iv, $config->getFieldValueEncoding()); 48 | 49 | // Generate an AES secret key 50 | $secretKey = AESEncryption::generateCek(self::SYMMETRIC_KEY_SIZE); 51 | 52 | // Encrypt the secret key 53 | $encryptedSecretKeyBytes = RSA::wrapSecretKey($config->getEncryptionCertificate()->getBytes(), $secretKey, $config->getOaepPaddingDigestAlgorithm()); 54 | $encryptedKeyValue = EncodingUtils::encodeBytes($encryptedSecretKeyBytes, $config->getFieldValueEncoding()); 55 | 56 | // Compute the OAEP padding digest algorithm 57 | $oaepPaddingDigestAlgorithmValue = str_replace('-', '', $config->getOaepPaddingDigestAlgorithm()); 58 | 59 | $params = new FieldLevelEncryptionParams($config, $ivValue, $encryptedKeyValue, $oaepPaddingDigestAlgorithmValue); 60 | $params->secretKey = $secretKey; 61 | $params->iv = $iv; 62 | return $params; 63 | } 64 | 65 | /** 66 | * @return string|null 67 | */ 68 | public function getIvValue() { 69 | return $this->ivValue; 70 | } 71 | 72 | /** 73 | * @return string|null 74 | */ 75 | public function getEncryptedKeyValue() { 76 | return $this->encryptedKeyValue; 77 | } 78 | 79 | /** 80 | * @return string|null 81 | */ 82 | public function getOaepPaddingDigestAlgorithmValue() { 83 | return $this->oaepPaddingDigestAlgorithmValue; 84 | } 85 | 86 | /** 87 | * @return string|false 88 | * @throws EncryptionException 89 | */ 90 | public function getIvBytes() { 91 | try { 92 | if (!empty($this->iv)) { 93 | return $this->iv; 94 | } 95 | // Decode the IV 96 | $this->iv = EncodingUtils::decodeValue($this->ivValue, $this->config->getFieldValueEncoding()); 97 | return $this->iv; 98 | } catch (Exception $e) { 99 | throw new EncryptionException('Failed to decode the provided IV value!', $e); 100 | } 101 | } 102 | 103 | /** 104 | * @return string 105 | * @throws EncryptionException 106 | */ 107 | public function getSecretKeyBytes() { 108 | try { 109 | if (!empty($this->secretKey)) { 110 | return $this->secretKey; 111 | } 112 | // Decrypt the AES secret key 113 | $encryptedSecretKeyBytes = EncodingUtils::decodeValue($this->encryptedKeyValue, $this->config->getFieldValueEncoding()); 114 | $decryptionKey = $this->config->getDecryptionKey(); 115 | $this->secretKey = RSA::unwrapSecretKey($decryptionKey->getBytes(), $encryptedSecretKeyBytes, $this->oaepPaddingDigestAlgorithmValue, $decryptionKey->getPassword()); 116 | return $this->secretKey; 117 | } catch (EncryptionException $e) { 118 | throw $e; 119 | } catch (Exception $e) { 120 | throw new EncryptionException('Failed to decode and unwrap the provided secret key value!', $e); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /src/Developer/Encryption/FieldValueEncoding.php: -------------------------------------------------------------------------------- 1 | alg = $alg; 15 | $this->enc = $enc; 16 | if (!is_null($kid)) { 17 | $this->kid = $kid; 18 | } 19 | if (!is_null($cty)) { 20 | $this->cty = $cty; 21 | } 22 | } 23 | 24 | public function toJSON() 25 | { 26 | $obj = []; 27 | 28 | if (isset($this->kid)) { 29 | $obj["kid"] = $this->kid; 30 | } 31 | if (isset($this->alg)) { 32 | $obj["alg"] = $this->alg; 33 | } 34 | if (isset($this->enc)) { 35 | $obj["enc"] = $this->enc; 36 | } 37 | if (isset($this->cty)) { 38 | $obj["cty"] = $this->cty; 39 | } 40 | 41 | return json_encode($obj); 42 | } 43 | 44 | public static function parseJweHeader($encodedHeader) 45 | { 46 | 47 | $headerObj = json_decode(base64_decode($encodedHeader), true); 48 | 49 | $alg = $headerObj["alg"]; 50 | $enc = $headerObj["enc"]; 51 | $kid = (isset($headerObj["kid"])) ? $headerObj["kid"] : null; 52 | $cty = (isset($headerObj["cty"])) ? $headerObj["cty"] : null; 53 | return new JweHeader($alg, $enc, $kid, $cty); 54 | } 55 | 56 | public function getEnc() 57 | { 58 | return $this->enc; 59 | } 60 | public function getAlg() 61 | { 62 | return $this->alg; 63 | } 64 | public function getKid() 65 | { 66 | return $this->kid; 67 | } 68 | public function getCty() 69 | { 70 | return $this->cty; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/Developer/Encryption/JWE/JweObject.php: -------------------------------------------------------------------------------- 1 | header = $header; 58 | $this->rawHeader = $rawHeader; 59 | $this->encryptedKey = $encryptedKey; 60 | $this->iv = $iv; 61 | $this->cipherText = $cipherText; 62 | $this->authTag = $authTag; 63 | } 64 | 65 | /** 66 | * @param JweConfig $config 67 | * @return string 68 | * @throws EncryptionException 69 | */ 70 | public function decrypt($config) 71 | { 72 | $cek = RSA::unwrapSecretKey($config->getDecryptionKey()->getBytes(), EncodingUtils::base64UrlDecode($this->getEncryptedKey())); 73 | $encryptionMethod = $this->header->getEnc(); 74 | 75 | if ($encryptionMethod == "A256GCM" || $encryptionMethod == "A128GCM" || $encryptionMethod == "A192GCM") { 76 | return AESGCM::decrypt( 77 | EncodingUtils::base64UrlDecode($this->getIv()), 78 | $cek, 79 | EncodingUtils::base64UrlDecode($this->getAuthTag()), 80 | $this->getRawHeader(), 81 | EncodingUtils::base64UrlDecode($this->getCipherText()) 82 | ); 83 | } elseif ($encryptionMethod == "A128CBC-HS256") { 84 | return AESCBC::decrypt( 85 | EncodingUtils::base64UrlDecode($this->getIv()), 86 | substr($cek, 16, 16), 87 | EncodingUtils::base64UrlDecode($this->getCipherText()) 88 | ); 89 | } else { 90 | throw new EncryptionException(sprintf("Encryption method %s not supported", $encryptionMethod)); 91 | } 92 | } 93 | 94 | /** 95 | * @param JweConfig $config 96 | * @param string $payload 97 | * @param JweHeader $header 98 | * @return string 99 | * @throws EncryptionException 100 | */ 101 | public static function encrypt($config, $payload, $header) 102 | { 103 | $cek = AESEncryption::generateCek(256); 104 | 105 | $encryptedSecretKeyBytes = RSA::wrapSecretKey($config->getEncryptionCertificate()->getBytes(), $cek); 106 | $encryptedKey = EncodingUtils::base64UrlEncode($encryptedSecretKeyBytes); 107 | 108 | $iv = AESEncryption::generateIv(); 109 | 110 | $headerString = $header->toJSON(); 111 | $encodedHeader = EncodingUtils::base64UrlEncode($headerString); 112 | 113 | $cipher = new AES('gcm'); 114 | $cipher->setNonce($iv); 115 | $cipher->setKey($cek); 116 | $cipher->disablePadding(); 117 | $cipher->setAAD($encodedHeader); 118 | $cipherText = $cipher->encrypt($payload); 119 | $authTag = $cipher->getTag(); 120 | 121 | return self::serialize( 122 | $encodedHeader, 123 | $encryptedKey, 124 | EncodingUtils::base64UrlEncode($iv), 125 | EncodingUtils::base64UrlEncode($cipherText), 126 | EncodingUtils::base64UrlEncode($authTag) 127 | ); 128 | } 129 | 130 | /** 131 | * @param string $header 132 | * @param string $encryptedKey 133 | * @param string $iv 134 | * @param string $cipherText 135 | * @param string $authTag 136 | * @return string 137 | */ 138 | private static function serialize($header, $encryptedKey, $iv, $cipherText, $authTag) 139 | { 140 | return "$header.$encryptedKey.$iv.$cipherText.$authTag"; 141 | } 142 | 143 | /** 144 | * @param string $encryptedPayload 145 | * @return JweObject 146 | */ 147 | public static function parse($encryptedPayload) 148 | { 149 | $t = trim($encryptedPayload); 150 | 151 | if (substr_count($t, '.') != 4) 152 | { 153 | throw new EncryptionException("Invalid payload"); 154 | } 155 | 156 | $dot1 = strpos($t, '.'); 157 | $dot2 = strpos($t, '.', $dot1 + 1); 158 | $dot3 = strpos($t, '.', $dot2 + 1); 159 | $dot4 = strpos($t, '.', $dot3 + 1); 160 | 161 | $header = JweHeader::parseJweHeader(substr($t, 0, $dot1)); 162 | 163 | return new JweObject( 164 | $header, 165 | substr($t, 0, $dot1), 166 | substr($t, $dot1 + 1, $dot2 - $dot1 - 1), 167 | substr($t, $dot2 + 1, $dot3 - $dot2 - 1), 168 | substr($t, $dot3 + 1, $dot4 - $dot3 - 1), 169 | substr($t, $dot4 + 1) 170 | ); 171 | } 172 | 173 | /** 174 | * @return JweObject 175 | */ 176 | public function getHeader() 177 | { 178 | return $this->header; 179 | } 180 | 181 | /** 182 | * @return string 183 | */ 184 | public function getRawHeader() 185 | { 186 | return $this->rawHeader; 187 | } 188 | 189 | /** 190 | * @return string 191 | */ 192 | private function getEncryptedKey() 193 | { 194 | return $this->encryptedKey; 195 | } 196 | 197 | /** 198 | * @return string 199 | */ 200 | public function getIv() 201 | { 202 | return $this->iv; 203 | } 204 | 205 | /** 206 | * @return string 207 | */ 208 | public function getCipherText() 209 | { 210 | return $this->cipherText; 211 | } 212 | 213 | /** 214 | * @return string 215 | */ 216 | public function getAuthTag() 217 | { 218 | return $this->authTag; 219 | } 220 | 221 | /** 222 | * @return string 223 | */ 224 | public function toJSON() 225 | { 226 | return json_encode([ 227 | "header" => json_decode($this->header->toJSON()), 228 | "encryptedKey" => $this->encryptedKey, 229 | "iv" => $this->iv, 230 | "cipherText" => $this->cipherText, 231 | "authTag" => $this->authTag, 232 | ]); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/Developer/Encryption/JweConfig.php: -------------------------------------------------------------------------------- 1 | scheme = EncryptionConfigScheme::JWE; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Developer/Encryption/JweConfigBuilder.php: -------------------------------------------------------------------------------- 1 | checkParameterValues(); 26 | $this->computeEncryptionKeyFingerprint($this->encryptionCertificate); 27 | $this->checkJsonPathParameterValues(); 28 | $config = new JweConfig(); 29 | $config->setEncryptionCertificate($this->encryptionCertificate); 30 | $config->setEncryptionKeyFingerprint($this->encryptionKeyFingerprint); 31 | $config->setDecryptionKey($this->decryptionKey); 32 | $config->setEncryptionPaths(empty($this->encryptionPaths) ? ["$" => "$"] : $this->encryptionPaths); 33 | $config->setDecryptionPaths(empty($this->decryptionPaths) ? ["$.encryptedData" => "$"] : $this->decryptionPaths); 34 | $config->setEncryptedValueFieldName($this->encryptedValueFieldName == null ? "encryptedData" : $this->encryptedValueFieldName); 35 | return $config; 36 | } 37 | 38 | /** 39 | * @param string $encryptionCertificate 40 | * @return JweConfigBuilder 41 | * See: {@link EncryptionConfig#encryptionCertificate}. 42 | */ 43 | public function withEncryptionCertificate($encryptionCertificate) { 44 | $this->encryptionCertificate = $encryptionCertificate; 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param string $decryptionKey 50 | * @return JweConfigBuilder 51 | * See: {@link EncryptionConfig#decryptionKey}. 52 | */ 53 | public function withDecryptionKey($decryptionKey) { 54 | $this->decryptionKey = $decryptionKey; 55 | return $this; 56 | } 57 | 58 | /** 59 | * @param string $jsonPathIn 60 | * @param string $jsonPathOut 61 | * @return JweConfigBuilder 62 | * See: {@link EncryptionConfig#encryptionPaths}. 63 | */ 64 | public function withEncryptionPath($jsonPathIn, $jsonPathOut) { 65 | $this->encryptionPaths[$jsonPathIn] = $jsonPathOut; 66 | return $this; 67 | } 68 | 69 | /** 70 | * @param string $jsonPathIn 71 | * @param string $jsonPathOut 72 | * @return JweConfigBuilder 73 | * See: {@link EncryptionConfig#decryptionPaths}. 74 | */ 75 | public function withDecryptionPath($jsonPathIn, $jsonPathOut) { 76 | $this->decryptionPaths[$jsonPathIn] = $jsonPathOut; 77 | return $this; 78 | } 79 | 80 | /** 81 | * @param string $encryptedValueFieldName 82 | * @return JweConfigBuilder 83 | * See: {@link EncryptionConfig#encryptedValueFieldName}. 84 | */ 85 | public function withEncryptedValueFieldName($encryptedValueFieldName) { 86 | $this->encryptedValueFieldName = $encryptedValueFieldName; 87 | return $this; 88 | } 89 | 90 | private function checkParameterValues() { 91 | if ($this->decryptionKey == null && $this->encryptionCertificate == null) { 92 | throw new \InvalidArgumentException("You must include at least an encryption certificate or a decryption key"); 93 | } 94 | } 95 | 96 | /** 97 | * @param mixed $encryptionCertificate 98 | * @throws EncryptionException 99 | */ 100 | private function computeEncryptionKeyFingerprint($encryptionCertificate) { 101 | if(isset($encryptionCertificate)) { 102 | try { 103 | $publicKey = openssl_pkey_get_public($encryptionCertificate->getBytes()); 104 | if($publicKey == false){ 105 | throw new EncryptionException('Invalid encryption cert'); 106 | } 107 | $publicKeyPem = openssl_pkey_get_details($publicKey)['key']; 108 | $publicKeyDer = EncodingUtils::pemToDer($publicKeyPem, '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----'); 109 | $hash = new Hash('sha256'); 110 | $this->encryptionKeyFingerprint = EncodingUtils::encodeBytes($hash->hash($publicKeyDer), FieldValueEncoding::HEX); 111 | } catch (\Exception $e) { 112 | throw new EncryptionException('Failed to compute encryption key fingerprint!', $e); 113 | } 114 | } 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/Developer/Encryption/JweEncryption.php: -------------------------------------------------------------------------------- 1 | getEncryptionPaths() as $jsonPathIn => $jsonPathOut) { 34 | $payloadJsonObject = self::encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config); 35 | } 36 | 37 | // Return the updated payload 38 | return json_encode($payloadJsonObject); 39 | } catch (\InvalidArgumentException $e) { 40 | throw $e; 41 | } catch (EncryptionException $e) { 42 | throw $e; 43 | } catch (\Exception $e) { 44 | throw new EncryptionException('Payload encryption failed!', $e); 45 | } 46 | } 47 | 48 | /** 49 | * Decrypt parts of a JSON payload using the given parameters and configuration. 50 | * @param string $payload A JSON string 51 | * @param JweConfig $config A FieldLevelEncryptionConfig instance 52 | * @return string The updated payload 53 | * @throws EncryptionException 54 | */ 55 | public static function decryptPayload($payload, $config) { 56 | try { 57 | $jsonObject = new JsonObject($payload, true); 58 | 59 | $ret = new JsonObject(); 60 | 61 | // Perform decryption (if needed) 62 | foreach ($config->getDecryptionPaths() as $jsonPathIn => $jsonPathOut) { 63 | $inJsonObject = $jsonObject->get($jsonPathIn); 64 | 65 | if (is_null($inJsonObject)){ 66 | continue; 67 | } 68 | 69 | $jweObject = JweObject::parse($inJsonObject); 70 | 71 | $decryptedPayload = $jweObject->decrypt($config); 72 | $parsedPayload = json_decode($decryptedPayload); 73 | 74 | $ret->set($jsonPathOut, json_last_error() === JSON_ERROR_NONE ? $parsedPayload : $decryptedPayload ); 75 | } 76 | 77 | // Return the updated payload 78 | return $ret->getJson(); 79 | } catch (\InvalidArgumentException $e) { 80 | throw $e; 81 | } catch (EncryptionException $e) { 82 | throw $e; 83 | } catch (\Throwable $e) { 84 | throw new EncryptionException('Payload decryption failed!', $e); 85 | } 86 | } 87 | 88 | /** 89 | * @param \stdClass $payloadJsonObject 90 | * @param string $jsonPathIn 91 | * @param string $jsonPathOut 92 | * @param JweConfig $config 93 | * @throws EncryptionException 94 | */ 95 | private static function encryptPayloadPath($payloadJsonObject, $jsonPathIn, $jsonPathOut, $config){ 96 | $inJsonObject = JsonPath::find($payloadJsonObject, $jsonPathIn); 97 | 98 | if (is_null($inJsonObject)) { 99 | // Nothing to encrypt 100 | return $payloadJsonObject; 101 | } 102 | 103 | $inJsonString = JsonUtils::sanitize(JsonUtils::toJsonString($inJsonObject)); 104 | $myHeader = new JweHeader(self::ALGORITHM, self::ENCRYPTION, $config->getEncryptionKeyFingerprint(), self::CONTENT_TYPE); 105 | $payload = JweObject::encrypt($config, $inJsonString, $myHeader); 106 | 107 | // Delete data in clear 108 | if ('$' !== $jsonPathIn) { 109 | JsonPath::delete($payloadJsonObject, $jsonPathIn); 110 | } else { 111 | $payloadJsonObject = json_decode('{}'); 112 | } 113 | 114 | // Add encrypted data and encryption fields at the given JSON path 115 | $outJsonObject = JsonUtils::checkOrCreateOutObject($payloadJsonObject, $jsonPathOut); 116 | $outJsonObject->{$config->getEncryptedValueFieldName()} = $payload; 117 | 118 | return $payloadJsonObject; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Developer/Encryption/RSA/RSA.php: -------------------------------------------------------------------------------- 1 | withHash($hash) 27 | ->withPadding(CryptRSA::ENCRYPTION_OAEP) 28 | ->withMGFHash($hash) 29 | ->encrypt($toWrap); 30 | } catch (\Exception $e) { 31 | throw new EncryptionException("Failed to wrap secret key!", $e); 32 | } 33 | } 34 | 35 | /** 36 | * @param string $decryptionKey 37 | * @param string $wrapped 38 | * @param string $oaepDigestAlgorithm 39 | * @param string|false $password 40 | * @return string 41 | * @throws EncryptionException 42 | */ 43 | public static function unwrapSecretKey($decryptionKey, $wrapped, $oaepDigestAlgorithm = 'sha256', $password = false) 44 | { 45 | $hash = strtolower(str_replace('-', '', $oaepDigestAlgorithm)); 46 | 47 | try { 48 | $asymmetricKey = PublicKeyLoader::load($decryptionKey, $password); 49 | 50 | return $asymmetricKey 51 | ->withHash($hash) 52 | ->withPadding(CryptRSA::ENCRYPTION_OAEP) 53 | ->withMGFHash($hash) 54 | ->decrypt($wrapped); 55 | } catch (\Exception $e) { 56 | throw new EncryptionException("Failed to unwrap secret key!", $e); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Developer/Interceptors/PsrHttpMessageEncryptionAbstractInterceptor.php: -------------------------------------------------------------------------------- 1 | getBody(); 29 | $payload = $body->__toString(); 30 | if (empty($payload)) { 31 | // Nothing to encrypt 32 | return $request; 33 | } 34 | 35 | $encryptedPayload = $this->encryptPayload($request, $payload); 36 | 37 | // Update body and content length 38 | $updatedBody = new PsrStreamInterfaceImpl(); 39 | $updatedBody->write($encryptedPayload); 40 | $request = $request->withBody($updatedBody); 41 | self::updateHeader($request, 'Content-Length', $updatedBody->getSize()); 42 | return $request; 43 | } catch (EncryptionException $e) { 44 | throw $e; 45 | } catch (\Throwable $e) { 46 | throw new EncryptionException('Failed to intercept and encrypt request!', $e); 47 | } 48 | } 49 | 50 | /** 51 | * Decrypt payloads from ResponseInterface objects when needed. 52 | * @param ResponseInterface $response A ResponseInterface object 53 | * @return ResponseInterface The updated ResponseInterface object 54 | * @throws EncryptionException 55 | */ 56 | public function interceptResponse(ResponseInterface &$response) 57 | { 58 | try { 59 | $body = $response->getBody(); 60 | $payload = $body->__toString(); 61 | if (empty($payload)) { 62 | // Nothing to decrypt 63 | return $response; 64 | } 65 | 66 | $decryptedPayload = $this->decryptPayload($response, $payload); 67 | 68 | // Update body and content length 69 | $updatedBody = new PsrStreamInterfaceImpl(); 70 | $updatedBody->write($decryptedPayload); 71 | $response = $response->withBody($updatedBody); 72 | self::updateHeader($response, 'Content-Length', $updatedBody->getSize()); 73 | return $response; 74 | } catch (EncryptionException $e) { 75 | throw $e; 76 | } catch (\Throwable $e) { 77 | throw new EncryptionException('Failed to intercept and encrypt request!', $e); 78 | } 79 | } 80 | 81 | /** 82 | * @param MessageInterface $message 83 | * @param string $name 84 | * @param string $value 85 | */ 86 | protected static function updateHeader(&$message, $name, $value) 87 | { 88 | if (empty($name)) { 89 | // Do nothing 90 | return $message; 91 | } 92 | $message = $message->withHeader($name, $value); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Developer/Interceptors/PsrHttpMessageEncryptionInterceptor.php: -------------------------------------------------------------------------------- 1 | config = $config; 30 | } 31 | 32 | 33 | public function encryptPayload(RequestInterface &$request, $requestPayload) 34 | { 35 | // Encrypt fields & update headers 36 | if ($this->config->useHttpHeaders()) { 37 | // Generate encryption params and add them as HTTP headers 38 | $params = FieldLevelEncryptionParams::generate($this->config); 39 | self::updateHeader($request, $this->config->getIvHeaderName(), $params->getIvValue()); 40 | self::updateHeader($request, $this->config->getEncryptedKeyHeaderName(), $params->getEncryptedKeyValue()); 41 | self::updateHeader($request, $this->config->getEncryptionCertificateFingerprintHeaderName(), $this->config->getEncryptionCertificateFingerprint()); 42 | self::updateHeader($request, $this->config->getEncryptionKeyFingerprintHeaderName(), $this->config->getEncryptionKeyFingerprint()); 43 | self::updateHeader($request, $this->config->getOaepPaddingDigestAlgorithmHeaderName(), $params->getOaepPaddingDigestAlgorithmValue()); 44 | $encryptedPayload = FieldLevelEncryption::encryptPayload($requestPayload, $this->config, $params); 45 | } else { 46 | // Encryption params will be stored in the payload 47 | $encryptedPayload = FieldLevelEncryption::encryptPayload($requestPayload, $this->config); 48 | } 49 | 50 | return $encryptedPayload; 51 | } 52 | 53 | public function decryptPayload(ResponseInterface &$response, $responsePayload) 54 | { 55 | // Decrypt fields & update headers 56 | if ($this->config->useHttpHeaders()) { 57 | // Read encryption params from HTTP headers and delete headers 58 | $ivValue = self::readAndRemoveHeader($response, $this->config->getIvHeaderName()); 59 | $encryptedKeyValue = self::readAndRemoveHeader($response, $this->config->getEncryptedKeyHeaderName()); 60 | $oaepPaddingDigestAlgorithmValue = self::readAndRemoveHeader($response, $this->config->getOaepPaddingDigestAlgorithmHeaderName()); 61 | self::readAndRemoveHeader($response, $this->config->getEncryptionCertificateFingerprintHeaderName()); 62 | self::readAndRemoveHeader($response, $this->config->getEncryptionKeyFingerprintHeaderName()); 63 | $params = new FieldLevelEncryptionParams($this->config, $ivValue, $encryptedKeyValue, $oaepPaddingDigestAlgorithmValue); 64 | $decryptedPayload = FieldLevelEncryption::decryptPayload($responsePayload, $this->config, $params); 65 | } else { 66 | // Encryption params are stored in the payload 67 | $decryptedPayload = FieldLevelEncryption::decryptPayload($responsePayload, $this->config); 68 | } 69 | 70 | return $decryptedPayload; 71 | } 72 | 73 | /** 74 | * @param MessageInterface $message 75 | * @param string $name 76 | * 77 | * @return string|null 78 | */ 79 | protected static function readAndRemoveHeader(&$message, $name) 80 | { 81 | if (empty($name)) { 82 | return null; 83 | } 84 | if (!$message->hasHeader($name)) { 85 | return null; 86 | } 87 | $values = $message->getHeader($name); 88 | $message = $message->withoutHeader($name); 89 | return $values[0]; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Developer/Interceptors/PsrHttpMessageJweInterceptor.php: -------------------------------------------------------------------------------- 1 | config = $config; 28 | } 29 | 30 | public function encryptPayload(RequestInterface &$request, $requestPayload) 31 | { 32 | return JweEncryption::encryptPayload($requestPayload, $this->config); 33 | } 34 | 35 | public function decryptPayload(ResponseInterface &$response, $responsePayload) 36 | { 37 | return JweEncryption::decryptPayload($responsePayload, $this->config); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Developer/Interceptors/PsrStreamInterfaceImpl.php: -------------------------------------------------------------------------------- 1 | content) ? $this->content : ''; 13 | } 14 | 15 | public function close() { 16 | unset($this->content); 17 | } 18 | 19 | public function detach() { 20 | unset($this->content); 21 | } 22 | 23 | public function getSize() { 24 | return isset($this->content) ? strlen($this->content) : 0; 25 | } 26 | 27 | public function tell() { 28 | return null; 29 | } 30 | 31 | public function eof() { 32 | return false; 33 | } 34 | 35 | public function isSeekable() { 36 | return false; 37 | } 38 | 39 | public function seek($offset, $whence = SEEK_SET) { 40 | return null; 41 | } 42 | 43 | public function rewind() { 44 | return null; 45 | } 46 | 47 | public function isWritable() { 48 | return false; 49 | } 50 | 51 | public function write($string) { 52 | $this->content = $string; 53 | } 54 | 55 | public function isReadable() { 56 | return true; 57 | } 58 | 59 | public function read($length) { 60 | return isset($this->content) ? substr($this->content, 0, $length) : ''; 61 | } 62 | 63 | public function getContents() { 64 | return $this->__toString(); 65 | } 66 | 67 | public function getMetadata($key = null) { 68 | return []; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Developer/Json/JsonPath.php: -------------------------------------------------------------------------------- 1 | $childKey; 76 | $currentPath = str_replace('[\'' . $childKey . '\']', '', $currentPath); 77 | } 78 | return $currentElement; 79 | } 80 | 81 | /** 82 | * Delete the element at the given path. 83 | * @param \stdClass $jsonObject 84 | * @param string|null $path 85 | * @throws \InvalidArgumentException 86 | */ 87 | static function delete($jsonObject, $path) { 88 | $parent = self::find($jsonObject, self::getParentPath($path)); 89 | if (is_null($parent)) { 90 | // Nothing to delete 91 | return; 92 | } 93 | $key = self::getElementKey($path); 94 | unset($parent->$key); 95 | } 96 | 97 | /** 98 | * Get JSON path to the parent of the object at the given JSON path. Example: 99 | * - Input: "$.['path'].['to'].['object']" 100 | * - Output: "$.['path'].['to']" 101 | * @param string|null $path 102 | * @return string 103 | * @throws \InvalidArgumentException 104 | */ 105 | static function getParentPath($path) { 106 | if ('$' === $path) { 107 | throw new \InvalidArgumentException('Unable to find parent for: ' . $path); 108 | } 109 | 110 | $normalizedPath = self::normalizePath($path); 111 | preg_match(self::LAST_TOKEN_IN_PATH, $normalizedPath, $matches); 112 | $size = sizeof($matches); 113 | if ($size > 0) { 114 | return str_replace($matches[1], '', $normalizedPath); 115 | } 116 | 117 | throw new \InvalidArgumentException('Unsupported JSON path: ' . $path . '!'); 118 | } 119 | 120 | /** 121 | * Get object key at the given JSON path. Example: 122 | * - Input: "$.['path'].['to'].['object']" 123 | * - Output: "object" 124 | * @param string|null $path 125 | * @return string 126 | * @throws \InvalidArgumentException 127 | */ 128 | static function getElementKey($path) { 129 | if ('$' === $path) { 130 | throw new \InvalidArgumentException('Unable to find object key for: ' . $path); 131 | } 132 | 133 | $normalizedPath = self::normalizePath($path); 134 | preg_match(self::LAST_CHILD_KEY, $normalizedPath, $matches); 135 | $size = sizeof($matches); 136 | if ($size > 0) { 137 | return $matches[1]; 138 | } 139 | 140 | throw new \InvalidArgumentException('Unsupported JSON path: ' . $path . '!'); 141 | } 142 | 143 | /** 144 | * Checks if a JSON path points to a single item or if it potentially returns multiple items. 145 | * @see https://github.com/json-path/JsonPath 146 | * @param string|null $path 147 | * @return bool 148 | * @throws \InvalidArgumentException 149 | */ 150 | static function isPathDefinite($path) { 151 | return strpos($path, '*') === false && strpos($path, '..') === false 152 | && strpos($path, '@') === false && strpos($path, ',') === false; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Developer/Json/JsonUtils.php: -------------------------------------------------------------------------------- 1 | $elementKey = json_decode('{}'); 78 | return $parentJsonObject->$elementKey; 79 | } 80 | 81 | /** 82 | * @param \stdClass $object 83 | * @param string $key 84 | * @return mixed 85 | */ 86 | public static function readAndDeleteJsonKey($object, $key) { 87 | if (empty($key) || false === property_exists($object, $key)) { 88 | // Do nothing 89 | return null; 90 | } 91 | $value = $object->$key; 92 | unset($object->$key); 93 | return $value; 94 | } 95 | 96 | /** 97 | * @param \stdClass $payloadJsonObject 98 | * @param string $jsonPathOut 99 | * @param \stdClass $outJsonObject 100 | * @param mixed $decryptedValue 101 | */ 102 | public static function addDecryptedDataToPayload($payloadJsonObject, $jsonPathOut, $outJsonObject, $decryptedValue) { 103 | $decryptedValueJsonElement = json_decode($decryptedValue); 104 | if (is_null($decryptedValueJsonElement)) { 105 | // 'json_decode' returns null for strings 106 | $decryptedValueJsonElement = $decryptedValue; 107 | } 108 | 109 | if ('$' === $jsonPathOut && is_array($decryptedValueJsonElement)) { 110 | return $decryptedValueJsonElement; 111 | } 112 | 113 | if (!is_object($decryptedValueJsonElement)) { 114 | // Array or primitive: overwrite 115 | $parentPath = JsonPath::getParentPath($jsonPathOut); 116 | $elementKey = JsonPath::getElementKey($jsonPathOut); 117 | $parentObject = JsonPath::find($payloadJsonObject, $parentPath); 118 | $parentObject->$elementKey = $decryptedValueJsonElement; 119 | return $payloadJsonObject; 120 | } 121 | 122 | // Object: merge 123 | foreach ($decryptedValueJsonElement as $key => $value) { 124 | $outJsonObject->$key = $value; 125 | } 126 | return $payloadJsonObject; 127 | } 128 | } -------------------------------------------------------------------------------- /src/Developer/Keys/DecryptionKey.php: -------------------------------------------------------------------------------- 1 | mPath = $keyPath; 36 | $ret->mAlias = $alias; 37 | $ret->mPassword = $password; 38 | try { 39 | $pkcs12_read_results = []; 40 | 41 | if(openssl_pkcs12_read(file_get_contents($keyPath), $pkcs12_read_results, $password)) { 42 | $ret->mContents = $pkcs12_read_results['pkey']; 43 | }else{ 44 | $ret->mContents = file_get_contents($keyPath); 45 | } 46 | } catch (\Exception $e) { 47 | throw new \InvalidArgumentException('Failed to read the given file: ' . $keyPath . '!', 0, $e); 48 | } 49 | 50 | return $ret; 51 | } 52 | 53 | /** 54 | * @return string 55 | */ 56 | public function getBytes(){ 57 | return $this->mContents; 58 | } 59 | 60 | /** 61 | * @return string|null 62 | */ 63 | public function getAlias(){ 64 | return $this->mAlias; 65 | } 66 | 67 | /** 68 | * @return string|null 69 | */ 70 | public function getPassword(){ 71 | return $this->mPassword; 72 | } 73 | } -------------------------------------------------------------------------------- /src/Developer/Keys/EncryptionKey.php: -------------------------------------------------------------------------------- 1 | mContents = file_get_contents($keyPath); 27 | if (strpos($ret->mContents, '-----BEGIN CERTIFICATE-----') === FALSE) { 28 | $ret->mContents = EncodingUtils::derToPem($ret->mContents, 29 | '-----BEGIN CERTIFICATE-----', 30 | '-----END CERTIFICATE-----' 31 | ); 32 | } 33 | } catch (\Exception $e) { 34 | throw new \InvalidArgumentException('Failed to read the given file: ' . $keyPath . '!', 0, $e); 35 | } 36 | 37 | return $ret; 38 | } 39 | 40 | /** 41 | * @param string $contents 42 | * @return EncryptionKey 43 | */ 44 | public static function create($contents){ 45 | $ret = new EncryptionKey(); 46 | $ret->mContents = $contents; 47 | return $ret; 48 | } 49 | 50 | /** 51 | * @return string 52 | */ 53 | public function getBytes(){ 54 | return $this->mContents; 55 | } 56 | } -------------------------------------------------------------------------------- /src/Developer/Utils/EncodingUtils.php: -------------------------------------------------------------------------------- 1 | = 0 && strpos($string, $suffix, $diff) !== false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/AES/AESCBCTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("bar", $result); 20 | } 21 | 22 | public function testEncryptBytes_InteroperabilityTest() { 23 | // GIVEN 24 | $ivValue = 'VNm/scgd1jhWF0z4+Qh6MA=='; 25 | $keyValue = 'mZzmzoURXI3Vk0vdsPkcFw=='; 26 | $dataValue = 'some data ù€@'; 27 | 28 | // WHEN 29 | $encryptedBytes = AESCBC::encrypt(base64_decode($ivValue), base64_decode($keyValue), $dataValue); 30 | 31 | // THEN 32 | $expectedEncryptedBytes = base64_decode('Y6X9YneTS4VuPETceBmvclrDoCqYyBgZgJUdnlZ8/0g='); 33 | $this->assertEquals($expectedEncryptedBytes, $encryptedBytes); 34 | } 35 | 36 | public function testDecryptBytes_InteroperabilityTest() { 37 | // GIVEN 38 | $ivValue = 'VNm/scgd1jhWF0z4+Qh6MA=='; 39 | $keyValue = 'mZzmzoURXI3Vk0vdsPkcFw=='; 40 | $encryptedDataValue = 'Y6X9YneTS4VuPETceBmvclrDoCqYyBgZgJUdnlZ8/0g='; 41 | 42 | // WHEN 43 | $decryptedBytes = AESCBC::decrypt(base64_decode($ivValue), base64_decode($keyValue), base64_decode($encryptedDataValue)); 44 | 45 | // THEN 46 | $expectedBytes = 'some data ù€@'; 47 | $this->assertEquals($expectedBytes, $decryptedBytes); 48 | } 49 | 50 | 51 | } 52 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/AES/AESEncryptionTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(32, strlen($cek)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/AES/AESGCMTest.php: -------------------------------------------------------------------------------- 1 | assertEquals("{\"foo\":\"bar\"}", $result); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/EncryptionExceptionTest.php: -------------------------------------------------------------------------------- 1 | expectException(EncryptionException::class); 12 | $this->expectExceptionMessage('Something happened!'); 13 | $this->expectExceptionCode(0); 14 | 15 | throw new EncryptionException('Something happened!', new \InvalidArgumentException()); 16 | } 17 | } -------------------------------------------------------------------------------- /tests/Developer/Encryption/FieldLevelEncryptionConfigBuilderTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionPath('$.payload', '$.encryptedPayload') 17 | ->withEncryptionCertificate(TestUtils::getTestEncryptionCertificate()) 18 | ->withEncryptionCertificateFingerprint('97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B') 19 | ->withEncryptionKeyFingerprint('F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810') 20 | ->withEncryptionCertificateFingerprintFieldName('publicCertificateFingerprint') 21 | ->withEncryptionCertificateFingerprintHeaderName('x-public-certificate-fingerprint') 22 | ->withEncryptionKeyFingerprintFieldName('publicKeyFingerprint') 23 | ->withEncryptionKeyFingerprintHeaderName('x-public-key-fingerprint') 24 | ->withDecryptionPath('$.encryptedPayload', '$.payload') 25 | ->withDecryptionKey(TestUtils::getTestDecryptionKey()) 26 | ->withOaepPaddingDigestAlgorithm('SHA-512') 27 | ->withOaepPaddingDigestAlgorithmFieldName('oaepPaddingDigestAlgorithm') 28 | ->withOaepPaddingDigestAlgorithmHeaderName('x-oaep-padding-digest-algorithm') 29 | ->withEncryptedValueFieldName('encryptedValue') 30 | ->withEncryptedKeyFieldName('encryptedKey') 31 | ->withEncryptedKeyHeaderName('x-encrypted-key') 32 | ->withIvFieldName('iv') 33 | ->withIvHeaderName('x-iv') 34 | ->withFieldValueEncoding(FieldValueEncoding::BASE64) 35 | ->build(); 36 | $this->assertNotEmpty($config); 37 | $this->assertEquals(1, sizeof($config->getEncryptionPaths())); 38 | $this->assertNotEmpty($config->getEncryptionCertificate()->getBytes()); 39 | $this->assertEquals('97A2FFE9F0D48960EF31E87FCD7A55BF7843FB4A9EEEF01BDB6032AD6FEF146B', $config->getEncryptionCertificateFingerprint()); 40 | $this->assertEquals('F806B26BC4870E26986C70B6590AF87BAF4C2B56BB50622C51B12212DAFF2810', $config->getEncryptionKeyFingerprint()); 41 | $this->assertEquals('publicCertificateFingerprint', $config->getEncryptionCertificateFingerprintFieldName()); 42 | $this->assertEquals('x-public-certificate-fingerprint', $config->getEncryptionCertificateFingerprintHeaderName()); 43 | $this->assertEquals('publicKeyFingerprint', $config->getEncryptionKeyFingerprintFieldName()); 44 | $this->assertEquals('x-public-key-fingerprint', $config->getEncryptionKeyFingerprintHeaderName()); 45 | $this->assertEquals(1, sizeof($config->getDecryptionPaths())); 46 | $this->assertNotEmpty($config->getDecryptionKey()); 47 | $this->assertEquals('SHA-512', $config->getOaepPaddingDigestAlgorithm()); 48 | $this->assertEquals('encryptedValue', $config->getEncryptedValueFieldName()); 49 | $this->assertEquals('encryptedKey', $config->getEncryptedKeyFieldName()); 50 | $this->assertEquals('x-encrypted-key', $config->getEncryptedKeyHeaderName()); 51 | $this->assertEquals('iv', $config->getIvFieldName()); 52 | $this->assertEquals('x-iv', $config->getIvHeaderName()); 53 | $this->assertEquals('oaepPaddingDigestAlgorithm', $config->getOaepPaddingDigestAlgorithmFieldName()); 54 | $this->assertEquals('x-oaep-padding-digest-algorithm', $config->getOaepPaddingDigestAlgorithmHeaderName()); 55 | $this->assertEquals(FieldValueEncoding::BASE64, $config->getFieldValueEncoding()); 56 | } 57 | 58 | public function testBuild_ShouldComputeCertificateAndKeyFingerprints_WhenFingerprintsNotSet() { 59 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 60 | ->withEncryptionCertificateFingerprint(null) 61 | ->withEncryptionKeyFingerprint(null) 62 | ->withEncryptionCertificate(TestUtils::getTestEncryptionCertificate()) 63 | ->withDecryptionKey(TestUtils::getTestDecryptionKey()) 64 | ->build(); 65 | $this->assertEquals('761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79', $config->getEncryptionKeyFingerprint()); 66 | $this->assertEquals('80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279', $config->getEncryptionCertificateFingerprint()); 67 | } 68 | 69 | public function testBuild_ShouldThrowEncryptionException_WhenFingerprintCanNotBeCalculated() { 70 | $this->expectException(EncryptionException::class); 71 | $this->expectExceptionMessage('Failed to compute encryption certificate fingerprint!'); 72 | TestUtils::getTestFieldLevelEncryptionConfigBuilder() 73 | ->withEncryptionCertificateFingerprint(null) 74 | ->withEncryptionCertificate(EncryptionKey::create('not a certificate')) 75 | ->build(); 76 | } 77 | 78 | public function testBuild_ShouldThrowInvalidArgumentException_WhenNotDefiniteDecryptionPath() { 79 | $this->expectException(InvalidArgumentException::class); 80 | $this->expectExceptionMessage('JSON paths for decryption must point to a single item!'); 81 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 82 | ->withDecryptionPath('$.encryptedPayloads[*]', '$.payload') 83 | ->withDecryptionKey(TestUtils::getTestDecryptionKey()) 84 | ->build(); 85 | } 86 | 87 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingDecryptionKey() { 88 | $this->expectException(InvalidArgumentException::class); 89 | $this->expectExceptionMessage('Can\'t decrypt without decryption key!'); 90 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 91 | ->withDecryptionPath('$.encryptedPayload', '$.payload') 92 | ->withOaepPaddingDigestAlgorithm('SHA-512') 93 | ->withEncryptedValueFieldName('encryptedValue') 94 | ->withEncryptedKeyFieldName('encryptedKey') 95 | ->withIvFieldName('iv') 96 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 97 | ->build(); 98 | } 99 | 100 | public function testBuild_ShouldThrowInvalidArgumentException_WhenNotDefiniteEncryptionPath() { 101 | $this->expectException(InvalidArgumentException::class); 102 | $this->expectExceptionMessage('JSON paths for encryption must point to a single item!'); 103 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 104 | ->withEncryptionPath('$.payloads[*]', '$.encryptedPayload') 105 | ->withEncryptionCertificate(TestUtils::getTestEncryptionCertificate()) 106 | ->build(); 107 | } 108 | 109 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingEncryptionCertificate() { 110 | $this->expectException(InvalidArgumentException::class); 111 | $this->expectExceptionMessage('Can\'t encrypt without encryption key!'); 112 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 113 | ->withEncryptionPath('$.payload', '$.encryptedPayload') 114 | ->withOaepPaddingDigestAlgorithm('SHA-512') 115 | ->withEncryptedValueFieldName('encryptedValue') 116 | ->withEncryptedKeyFieldName('encryptedKey') 117 | ->withIvFieldName('iv') 118 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 119 | ->build(); 120 | } 121 | 122 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingOaepPaddingDigestAlgorithm() { 123 | $this->expectException(InvalidArgumentException::class); 124 | $this->expectExceptionMessage('The digest algorithm for OAEP cannot be empty!'); 125 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 126 | ->withEncryptedValueFieldName('encryptedValue') 127 | ->withEncryptedKeyFieldName('encryptedKey') 128 | ->withIvFieldName('iv') 129 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 130 | ->build(); 131 | } 132 | 133 | public function testBuild_ShouldThrowInvalidArgumentException_WhenUnsupportedOaepPaddingDigestAlgorithm() { 134 | $this->expectException(InvalidArgumentException::class); 135 | $this->expectExceptionMessage('Unsupported OAEP digest algorithm: SHA-720!'); 136 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 137 | ->withOaepPaddingDigestAlgorithm('SHA-720') 138 | ->build(); 139 | } 140 | 141 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingEncryptedValueFieldName() { 142 | $this->expectException(InvalidArgumentException::class); 143 | $this->expectExceptionMessage('Encrypted value field name cannot be empty!'); 144 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 145 | ->withOaepPaddingDigestAlgorithm('SHA-512') 146 | ->withEncryptedKeyFieldName('encryptedKey') 147 | ->withIvFieldName('iv') 148 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 149 | ->build(); 150 | } 151 | 152 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingBothEncryptedKeyFieldNameAndHeaderName() { 153 | $this->expectException(InvalidArgumentException::class); 154 | $this->expectExceptionMessage('At least one of encrypted key field name or encrypted key header name must be set!'); 155 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 156 | ->withOaepPaddingDigestAlgorithm('SHA-512') 157 | ->withEncryptedValueFieldName('encryptedValue') 158 | ->withIvFieldName('iv') 159 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 160 | ->build(); 161 | } 162 | 163 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingBothIvFieldNameAndHeaderName() { 164 | $this->expectException(InvalidArgumentException::class); 165 | $this->expectExceptionMessage('At least one of IV field name or IV header name must be set!'); 166 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 167 | ->withOaepPaddingDigestAlgorithm('SHA-512') 168 | ->withEncryptedValueFieldName('encryptedValue') 169 | ->withEncryptedKeyFieldName('encryptedKey') 170 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 171 | ->build(); 172 | } 173 | 174 | public function testBuild_ShouldThrowInvalidArgumentException_WhenMissingFieldValueEncoding() { 175 | $this->expectException(InvalidArgumentException::class); 176 | $this->expectExceptionMessage('Value encoding for fields and headers cannot be empty!'); 177 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 178 | ->withOaepPaddingDigestAlgorithm('SHA-512') 179 | ->withEncryptedValueFieldName('encryptedValue') 180 | ->withEncryptedKeyFieldName('encryptedKey') 181 | ->withIvFieldName('iv') 182 | ->build(); 183 | } 184 | 185 | public function testBuild_ShouldThrowInvalidArgumentException_WhenEncryptedKeyAndIvHeaderNamesNotBothSetOrUnset() { 186 | $this->expectException(InvalidArgumentException::class); 187 | $this->expectExceptionMessage('IV header name and encrypted key header name must be both set or both unset!'); 188 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 189 | ->withOaepPaddingDigestAlgorithm('SHA-512') 190 | ->withEncryptedValueFieldName('encryptedValue') 191 | ->withEncryptedKeyHeaderName('x-encrypted-key') 192 | ->withEncryptedKeyFieldName('encryptedKey') 193 | ->withIvFieldName('iv') 194 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 195 | ->build(); 196 | } 197 | 198 | public function testBuild_ShouldThrowInvalidArgumentException_WhenEncryptedKeyAndIvFieldNamesNotBothSetOrUnset() { 199 | $this->expectException(InvalidArgumentException::class); 200 | $this->expectExceptionMessage('IV field name and encrypted key field name must be both set or both unset!'); 201 | FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 202 | ->withOaepPaddingDigestAlgorithm('SHA-512') 203 | ->withEncryptedValueFieldName('encryptedValue') 204 | ->withEncryptedKeyFieldName('encryptedKey') 205 | ->withEncryptedKeyHeaderName('x-encrypted-key') 206 | ->withIvHeaderName('x-iv') 207 | ->withFieldValueEncoding(FieldValueEncoding::HEX) 208 | ->build(); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/FileEncryptionParamsTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionCertificateFingerprint(null) 15 | ->withEncryptionKeyFingerprint(null) 16 | ->build(); 17 | 18 | // WHEN 19 | $params = FieldLevelEncryptionparams::generate($config); 20 | 21 | // THEN 22 | $this->assertNotEmpty($params->getIvValue()); 23 | $this->assertTrue(ctype_xdigit($params->getIvValue())); 24 | $this->assertNotEmpty($params->getIvBytes()); 25 | $this->assertNotEmpty($params->getEncryptedKeyValue()); 26 | $this->assertTrue(ctype_xdigit($params->getEncryptedKeyValue())); 27 | $this->assertNotEmpty($params->getSecretKeyBytes()); 28 | $this->assertEquals('SHA256', $params->getOaepPaddingDigestAlgorithmValue()); 29 | } 30 | 31 | public function testGetIvBytes_ShouldThrowEncryptionException_WhenFailsToDecodeIV() { 32 | 33 | // GIVEN 34 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()->build(); 35 | $params = new FieldLevelEncryptionParams($config, 'INVALID VALUE', null); 36 | 37 | // THEN 38 | $this->expectException(EncryptionException::class); 39 | $this->expectExceptionMessage('Failed to decode the provided IV value!'); 40 | 41 | // WHEN 42 | $params->getIvBytes(); 43 | } 44 | 45 | public function testGetSecretKeyBytes_ShouldThrowEncryptionException_WhenFailsToReadEncryptedKey() { 46 | 47 | // GIVEN 48 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()->build(); 49 | $params = new FieldLevelEncryptionParams($config, null, 'INVALID VALUE'); 50 | 51 | // THEN 52 | $this->expectException(EncryptionException::class); 53 | $this->expectExceptionMessage('Failed to decode and unwrap the provided secret key value!'); 54 | 55 | // WHEN 56 | $params->getSecretKeyBytes(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/JWE/JweHeaderTest.php: -------------------------------------------------------------------------------- 1 | assertEquals( 15 | json_decode($subjectStr), 16 | json_decode($header->toJson()) 17 | ); 18 | } 19 | 20 | public function testParseJweHeader_ShouldCorrectlyParseJweHeader() 21 | { 22 | $header = JweHeader::parseJweHeader("eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0"); 23 | $this->assertEquals("A256GCM", $header->getEnc()); 24 | $this->assertEquals("RSA-OAEP-256", $header->getAlg()); 25 | $this->assertEquals("application/json", $header->getCty()); 26 | $this->assertEquals("761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79", $header->getKid()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/JWE/JweObjectTest.php: -------------------------------------------------------------------------------- 1 | getDecryptionKey() 23 | ->thenReturn(DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem")); 24 | 25 | $decryptedPayload = $jweObject->decrypt($mockConfig); 26 | 27 | $this->assertEquals("{\"foo\":\"bar\"}", $decryptedPayload); 28 | } 29 | 30 | public function testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIs192GcmEncrypted() 31 | { 32 | $jweObject = JweObject::parse("eyJlbmMiOiJBMTkyR0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.FWC8PVaZoR2TRKwKO4syhSJReezVIvtkxU_yKh4qODNvlVr8t8ttvySJ-AjM8xdI6vNyIg9jBMWASG4cE49jT9FYuQ72fP4R-Td4vX8wpB8GonQj40yLqZyfRLDrMgPR20RcQDW2ThzLXsgI55B5l5fpwQ9Nhmx8irGifrFWOcJ_k1dUSBdlsHsYxkjRKMENu5x4H6h12gGZ21aZSPtwAj9msMYnKLdiUbdGmGG_P8a6gPzc9ih20McxZk8fHzXKujjukr_1p5OO4o1N4d3qa-YI8Sns2fPtf7xPHnwi1wipmCC6ThFLU80r3173RXcpyZkF8Y3UacOS9y1f8eUfVQ.JRE7kZLN4Im1Rtdb.eW_lJ-U330n0QHqZnQ._r5xYVvMCrvICwLz4chjdw"); 33 | 34 | $mockConfig = Phake::mock(JweConfig::class); 35 | 36 | Phake::when($mockConfig)->getDecryptionKey() 37 | ->thenReturn(DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem")); 38 | 39 | $decryptedPayload = $jweObject->decrypt($mockConfig); 40 | 41 | $this->assertEquals("{\"foo\":\"bar\"}", $decryptedPayload); 42 | } 43 | 44 | public function testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIs128GcmEncrypted() 45 | { 46 | $jweObject = JweObject::parse("eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.WtvYljbsjdEv-Ttxx1p6PgyIrOsLpj1FMF9NQNhJUAHlKchAo5QImgEgIdgJE7HC2KfpNcHiQVqKKZq_y201FVzpicDkNzlPJr5kIH4Lq-oC5iP0agWeou9yK5vIxFRP__F_B8HSuojBJ3gDYT_KdYffUIHkm_UysNj4PW2RIRlafJ6RKYanVzk74EoKZRG7MIr3pTU6LIkeQUW41qYG8hz6DbGBOh79Nkmq7Oceg0ZwCn1_MruerP-b15SGFkuvOshStT5JJp7OOq82gNAOkMl4fylEj2-vADjP7VSK8GlqrA7u9Tn-a4Q28oy0GOKr1Z-HJgn_CElknwkUTYsWbg.PKl6_kvZ4_4MjmjW.AH6pGFkn7J49hBQcwg.zdyD73TcuveImOy4CRnVpw"); 47 | 48 | $mockConfig = Phake::mock(JweConfig::class); 49 | 50 | Phake::when($mockConfig)->getDecryptionKey() 51 | ->thenReturn(DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem")); 52 | 53 | $decryptedPayload = $jweObject->decrypt($mockConfig); 54 | 55 | $this->assertEquals("{\"foo\":\"bar\"}", $decryptedPayload); 56 | } 57 | 58 | public function testDecrypt_ShouldReturnDecryptedPayload_WhenPayloadIsCbcEncrypted() 59 | { 60 | $jweObject = JweObject::parse("eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.5bsamlChk0HR3Nqg2UPJ2Fw4Y0MvC2pwWzNv84jYGkOXyqp1iwQSgETGaplIa7JyLg1ZWOqwNHEx3N7gsN4nzwAnVgz0eta6SsoQUE9YQ-5jek0COslUkoqIQjlQYJnYur7pqttDibj87fcw13G2agle5fL99j1QgFPjNPYqH88DMv481XGFa8O3VfJhW93m73KD2gvE5GasOPOkFK9wjKXc9lMGSgSArp3Awbc_oS2Cho_SbsvuEQwkhnQc2JKT3IaSWu8yK7edNGwD6OZJLhMJzWJlY30dUt2Eqe1r6kMT0IDRl7jHJnVIr2Qpe56CyeZ9V0aC5RH1mI5dYk4kHg.yI0CS3NdBrz9CCW2jwBSDw.6zr2pOSmAGdlJG0gbH53Eg.UFgf3-P9UjgMocEu7QA_vQ"); 61 | 62 | $mockConfig = Phake::mock(JweConfig::class); 63 | 64 | Phake::when($mockConfig)->getDecryptionKey() 65 | ->thenReturn(DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem")); 66 | 67 | $decryptedPayload = $jweObject->decrypt($mockConfig); 68 | 69 | $this->assertEquals("bar", $decryptedPayload); 70 | } 71 | 72 | public function testEncryptWithGCM() 73 | { 74 | $mockConfig = Phake::mock(JweConfig::class); 75 | 76 | Phake::when($mockConfig)->getEncryptionCertificate() 77 | ->thenReturn(EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem")); 78 | 79 | Phake::when($mockConfig)->getDecryptionKey() 80 | ->thenReturn(DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem")); 81 | 82 | Phake::when($mockConfig)->getEncryptionKeyFingerprint() 83 | ->thenReturn("761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79"); 84 | 85 | $jweHeader = new JweHeader("RSA-OAEP-256", "A256GCM", $mockConfig->getEncryptionKeyFingerprint(), "application/json"); 86 | 87 | $originalPayload = "Hello world"; 88 | 89 | $jwePayload = JweObject::encrypt($mockConfig, $originalPayload, $jweHeader); 90 | $jweObject = JweObject::parse($jwePayload); 91 | 92 | $decryptedPayload = $jweObject->decrypt($mockConfig); 93 | 94 | $this->assertEquals($originalPayload, $decryptedPayload); 95 | } 96 | 97 | public function testDecryptWithA128CBC_HS256Encryption() 98 | { 99 | $jweObj = JweObject::parse("eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.5bsamlChk0HR3Nqg2UPJ2Fw4Y0MvC2pwWzNv84jYGkOXyqp1iwQSgETGaplIa7JyLg1ZWOqwNHEx3N7gsN4nzwAnVgz0eta6SsoQUE9YQ-5jek0COslUkoqIQjlQYJnYur7pqttDibj87fcw13G2agle5fL99j1QgFPjNPYqH88DMv481XGFa8O3VfJhW93m73KD2gvE5GasOPOkFK9wjKXc9lMGSgSArp3Awbc_oS2Cho_SbsvuEQwkhnQc2JKT3IaSWu8yK7edNGwD6OZJLhMJzWJlY30dUt2Eqe1r6kMT0IDRl7jHJnVIr2Qpe56CyeZ9V0aC5RH1mI5dYk4kHg.yI0CS3NdBrz9CCW2jwBSDw.6zr2pOSmAGdlJG0gbH53Eg.UFgf3-P9UjgMocEu7QA_vQ"); 100 | 101 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 102 | 103 | $config = JweConfigBuilder::aJweEncryptionConfig() 104 | ->withDecryptionKey($decryptionKey) 105 | ->build(); 106 | 107 | $result = $jweObj->decrypt($config); 108 | $this->assertEquals("bar", $result); 109 | } 110 | 111 | 112 | public function testParse() 113 | { 114 | $jweObj = JweObject::parse("eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.8c6vxeZOUBS8A9SXYUSrRnfl1ht9xxciB7TAEv84etZhQQ2civQKso-htpa2DWFBSUm-UYlxb6XtXNXZxuWu-A0WXjwi1K5ZAACc8KUoYnqPldEtC9Q2bhbQgc_qZF_GxeKrOZfuXc9oi45xfVysF_db4RZ6VkLvY2YpPeDGEMX_nLEjzqKaDz_2m0Ae_nknr0p_Nu0m5UJgMzZGR4Sk1DJWa9x-WJLEyo4w_nRDThOjHJshOHaOU6qR5rdEAZr_dwqnTHrjX9Qm9N9gflPGMaJNVa4mvpsjz6LJzjaW3nJ2yCoirbaeJyCrful6cCiwMWMaDMuiBDPKa2ovVTy0Sw.w0Nkjxl0T9HHNu4R.suRZaYu6Ui05Z3-vsw.akknMr3Dl4L0VVTGPUszcA"); 115 | 116 | $this->assertEquals( 117 | '{"kid":"761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79","alg":"RSA-OAEP-256","enc":"A256GCM","cty":"application\/json"}', 118 | $jweObj->getHeader()->toJSON() 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/JweConfigBuilderTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionCertificate($encryptionCerificate) 18 | ->withDecryptionKey($decryptionKey) 19 | ->withEncryptionPath("$", "$") 20 | ->withDecryptionPath("$.encryptedPayload", "$") 21 | ->withEncryptedValueFieldName("encryptedPayload") 22 | ->build(); 23 | 24 | $this->assertNotEmpty($config); 25 | $this->assertEquals(EncryptionConfigScheme::JWE, $config->getScheme()); 26 | $this->assertEquals($decryptionKey, $config->getDecryptionKey()); 27 | $this->assertEquals($encryptionCerificate->getBytes(), $config->getEncryptionCertificate()->getBytes()); 28 | $this->assertEquals("encryptedPayload", $config->getEncryptedValueFieldName()); 29 | $this->assertEquals(["$.encryptedPayload" => "$"], $config->getDecryptionPaths()); 30 | $this->assertEquals(["$" => "$"], $config->getEncryptionPaths()); 31 | } 32 | 33 | public function testBuild_ResultShouldBeAssignableToGenericEncryptionConfig() { 34 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 35 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 36 | 37 | $config = JweConfigBuilder::aJweEncryptionConfig() 38 | ->withEncryptionCertificate($encryptionCerificate) 39 | ->withDecryptionKey($decryptionKey) 40 | ->build(); 41 | 42 | $this->assertNotNull($config); 43 | } 44 | 45 | public function testBuild_ShouldComputeCertificateKeyFingerprint_WhenFingerprintNotSet() { 46 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 47 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 48 | 49 | $config = JweConfigBuilder::aJweEncryptionConfig() 50 | ->withEncryptionCertificate($encryptionCerificate) 51 | ->withDecryptionKey($decryptionKey) 52 | ->build(); 53 | 54 | $this->assertEquals("761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79", $config->getEncryptionKeyFingerprint()); 55 | } 56 | 57 | public function testIntercept_ShouldThrowEncryptionException_WhenInvalidEncryptionCertificate() { 58 | $this->expectException("Mastercard\Developer\Encryption\EncryptionException"); 59 | $this->expectExceptionMessage("Failed to compute encryption key fingerprint!"); 60 | 61 | JweConfigBuilder::aJweEncryptionConfig() 62 | ->withEncryptionPath("$.foo", "$.encryptedFoo") 63 | ->withEncryptionCertificate(EncryptionKey::create("Invalid certificate")) // Invalid certificate 64 | ->build(); 65 | } 66 | 67 | public function testBuild_ShouldFallbackToDefaults() { 68 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 69 | 70 | $config = JweConfigBuilder::aJweEncryptionConfig() 71 | ->withEncryptionCertificate($encryptionCerificate) 72 | ->build(); 73 | 74 | $this->assertEquals(["$.encryptedData" => "$"], $config->getDecryptionPaths()); 75 | $this->assertEquals(["$" => "$"], $config->getEncryptionPaths()); 76 | $this->assertEquals("encryptedData", $config->getEncryptedValueFieldName()); 77 | } 78 | 79 | public function testBuild_ShouldThrowIllegalArgumentException_WhenMissingDecryptionKey() { 80 | $this->expectException("\InvalidArgumentException"); 81 | $this->expectExceptionMessage("JSON paths for decryption must point to a single item!"); 82 | 83 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 84 | 85 | JweConfigBuilder::aJweEncryptionConfig() 86 | ->withDecryptionPath("$.encryptedPayloads[*]", "$.payload") 87 | ->withDecryptionKey($decryptionKey) 88 | ->build(); 89 | } 90 | 91 | public function testBuild_ShouldThrowIllegalArgumentException_WhenNotDefiniteEncryptionPath() { 92 | $this->expectException("\InvalidArgumentException"); 93 | $this->expectExceptionMessage("JSON paths for decryption must point to a single item!"); 94 | 95 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 96 | 97 | JweConfigBuilder::aJweEncryptionConfig() 98 | ->withEncryptionPath("$.payloads[*]", "$.encryptedPayload") 99 | ->withEncryptionCertificate($encryptionCerificate) 100 | ->build(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/JweEncryptionTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionCertificate($encryptionCerificate) 22 | ->withDecryptionKey($decryptionKey) 23 | ->withEncryptionPath("$", "$") 24 | ->withDecryptionPath("$.encryptedData", "$") 25 | ->build(); 26 | 27 | 28 | // WHEN 29 | $encryptedPayload = JweEncryption::encryptPayload($payload, $config); 30 | 31 | // THEN 32 | $this->assertNotNull(json_encode($encryptedPayload)); 33 | 34 | $decrypedPayload = JweEncryption::decryptPayload($encryptedPayload, $config); 35 | 36 | $this->assertJsonStringEqualsJsonString("[{},{}]", $decrypedPayload); 37 | } 38 | 39 | public function testDecryptPayload_ShouldDecryptRootArrays() { 40 | 41 | // GIVEN 42 | $encryptedPayload = "{" . 43 | " \"encryptedData\": \"eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb24vanNvbiIsImVuYyI6IkEyNTZHQ00iLCJhbGciOiJSU0EtT0FFUC0yNTYifQ.IcTIce59pgtjODJn4PhR7oK3F-gxcd7dishTrT7T9y5VC0U5ZS_JdMoRe59_UTkJMY8Nykb2rv3Oh_jSDYRmGB_CWMIciXYMLHQptLTF5xI1ZauDPnooDMWoOCBD_d3I0wTJNcM7I658rK0ZWSByVK9YqhEo8UaIf4e6egRHQdZ2_IGKgICwmglv_uXQrYewOWFTKR1uMpya1N50MDnWax2NtnW3SljP3mARUBLBnRmOyubQCg-Mgn8fsOWWXm-KL9RrQq9AF_HJceoJl1rRgzPW7g6SLK6EjiGW_ArTmrLaOHg9bYOY_LrbyokK_M1pMo9qup70DHvjHkMZqIL3aQ.vtma3jBIo2STkquxTUX9PQ.9ZoQG0sFvQ.ms4bW3OFd03neRlex-zZ8w\"" . 44 | "}"; 45 | 46 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 47 | 48 | $config = JweConfigBuilder::aJweEncryptionConfig() 49 | ->withDecryptionKey($decryptionKey) 50 | ->withDecryptionPath("$.encryptedData", "$") 51 | ->build(); 52 | 53 | // WHEN 54 | $decrypedPayload = JweEncryption::decryptPayload($encryptedPayload, $config); 55 | 56 | // THEN 57 | $this->assertJsonStringEqualsJsonString("[{},{}]", $decrypedPayload); 58 | } 59 | 60 | public function testSample() 61 | { 62 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 63 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 64 | 65 | $config = JweConfigBuilder::aJweEncryptionConfig() 66 | ->withEncryptionCertificate($encryptionCerificate) 67 | ->withDecryptionKey($decryptionKey) 68 | ->withEncryptionPath("$.path.to.foo", "$.path.to.encryptedFoo") 69 | ->withDecryptionPath("$.path.to.encryptedFoo.encryptedData", "$.path.to.foo") 70 | ->build(); 71 | 72 | $payload = "{" . 73 | " \"path\": {" . 74 | " \"to\": {" . 75 | " \"foo\": {" . 76 | " \"sensitiveField1\": \"sensitiveValue1\"," . 77 | " \"sensitiveField2\": \"sensitiveValue2\"" . 78 | " }" . 79 | " }" . 80 | " }" . 81 | "}"; 82 | 83 | $encryptedPayload = JweEncryption::encryptPayload($payload, $config); 84 | 85 | $decryptedPayload = JweEncryption::decryptPayload($encryptedPayload, $config); 86 | 87 | $this->assertEquals(json_encode(json_decode($payload)), $decryptedPayload); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/Developer/Encryption/RSA/RSATest.php: -------------------------------------------------------------------------------- 1 | assertEquals($originalKeyBytes, $decrypedKeyBytes); 18 | } 19 | 20 | 21 | 22 | public function testUnwrapSecretKey_InteroperabilityTest_OaepSha256() 23 | { 24 | $wrappedKeyBytes = base64_decode("ZLB838BRWW2/BtdFFAWBRYShw/gBxXSwItpxEZ9zaSVEDHo7n+SyVYU7mayd+9vHkR8OdpqwpXM68t0VOrWI8LD8A2pRaYx8ICyhVFya4OeiWlde05Rhsk+TNwwREPbiw1RgjT8aedRJJYbAZdLb9XEI415Kb/UliHyvsdHMb6vKyYIjUHB/pSGAAmgds56IhIJGfvnBLPZfSHmGgiBT8WXLRuuf1v48aIadH9S0FfoyVGTaLYr+2eznSTAFC0ZBnzebM3mQI5NGQNviTnEJ0y+uZaLE/mthiKgkv1ZybyDPx2xJK2n05sNzfIWKmnI/SOb65RZLlo1Q+N868l2m9g=="); 25 | $decrypedKeyBytes = RSA::unwrapSecretKey(file_get_contents("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"), $wrappedKeyBytes, 'sha256'); 26 | $expectedKeyBytes = base64_decode("mZzmzoURXI3Vk0vdsPkcFw=="); 27 | 28 | $this->assertEquals($expectedKeyBytes, $decrypedKeyBytes); 29 | } 30 | 31 | public function testUnwrapSecretKey_InteroperabilityTest_OaepSha512() 32 | { 33 | $wrappedKeyBytes = base64_decode("RuruMYP5rG6VP5vS4kVznIrSOjUzXyOhtD7bYlVqwniWTvxxZC73UDluwDhpLwX5QJCsCe8TcwGiQRX1u+yWpBveHDRmDa03hrc3JRJALEKPyN5tnt5w7aI4dLRnLuNoXbYoTSc4V47Z3gaaK6q2rEjydx2sQ/SyVmeUJN7NgxkhtHTyVWTymEM1ythL+AaaQ5AaXedhpWKhG06XYZIX4KV7T9cHEn+See6RVGGB2RUPHBJjrxJo5JoVSfnWN0gkTMyuwbmVaTWfsowbvh8GFibFT7h3uXyI3b79NiauyB7scXp9WidGues3MrTx4dKZrSbs3uHxzPKmCDZimuKfwg=="); 34 | $decrypedKeyBytes = RSA::unwrapSecretKey(file_get_contents("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"), $wrappedKeyBytes, 'sha512'); 35 | $expectedKeyBytes = base64_decode("mZzmzoURXI3Vk0vdsPkcFw=="); 36 | 37 | $this->assertEquals($expectedKeyBytes, $decrypedKeyBytes); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/Developer/Interceptors/PsrHttpMessageFieldLevelEncryptionInterceptorTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionPath('$.foo', '$.encryptedFoo') 21 | ->build(); 22 | $payload = '{"foo":"bår"}'; 23 | $headers = ['Content-Type' => 'application/json']; 24 | $request = new Request('POST', 'https://api.mastercard.com/service', $headers, $payload); 25 | 26 | // WHEN 27 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 28 | $outRequest = $instanceUnderTest->interceptRequest($request); 29 | 30 | // THEN 31 | $this->assertSame($outRequest, $request); 32 | $encryptedPayload = $request->getBody()->__toString(); 33 | $this->assertFalse(StringUtils::contains($encryptedPayload, 'foo')); 34 | $this->assertTrue(StringUtils::contains($encryptedPayload, 'encryptedFoo')); 35 | $this->assertEquals(1, sizeof($request->getHeader('Content-Length'))); 36 | $this->assertEquals(strval(strlen($encryptedPayload)), $request->getHeader('Content-Length')[0]); 37 | } 38 | 39 | public function testInterceptRequest_ShouldDoNothing_WhenNoPayload() 40 | { 41 | 42 | // GIVEN 43 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 44 | ->withEncryptionPath('$.foo', '$.encryptedFoo') 45 | ->build(); 46 | $request = new Request('GET', 'https://api.mastercard.com/service'); 47 | $initialHeaderCount = sizeof($request->getHeaders()); 48 | 49 | // WHEN 50 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 51 | $outRequest = $instanceUnderTest->interceptRequest($request); 52 | 53 | // THEN 54 | $this->assertSame($outRequest, $request); 55 | $this->assertEmpty($request->getBody()->__toString()); 56 | $this->assertEquals($initialHeaderCount, sizeof($request->getHeaders())); 57 | } 58 | 59 | public function testInterceptRequest_ShouldThrowEncryptionException_WhenEncryptionFails() 60 | { 61 | 62 | // GIVEN 63 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 64 | ->withEncryptionPath('$.foo', '$.encryptedFoo') 65 | ->withEncryptionCertificate(TestUtils::getTestInvalidEncryptionCertificate()) // Invalid certificate 66 | ->build(); 67 | $payload = '{"foo":"bår"}'; 68 | $headers = ['Content-Type' => 'application/json']; 69 | $request = new Request('POST', 'https://api.mastercard.com/service', $headers, $payload); 70 | 71 | // THEN 72 | $this->expectException(EncryptionException::class); 73 | $this->expectExceptionMessage('Failed to wrap secret key!'); 74 | 75 | // WHEN 76 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 77 | $instanceUnderTest->interceptRequest($request); 78 | } 79 | 80 | public function testInterceptRequest_ShouldEncryptRequestPayloadAndAddEncryptionHttpHeaders_WhenRequestedInConfig() 81 | { 82 | 83 | // GIVEN 84 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 85 | ->withEncryptionPath('$.foo', '$.encryptedFoo') 86 | ->withIvHeaderName('x-iv') 87 | ->withEncryptedKeyHeaderName('x-encrypted-key') 88 | ->withOaepPaddingDigestAlgorithmHeaderName('x-oaep-padding-digest-algorithm') 89 | ->withEncryptionCertificateFingerprintHeaderName('x-encryption-certificate-fingerprint') 90 | ->withEncryptionKeyFingerprintHeaderName('x-encryption-key-fingerprint') 91 | ->build(); 92 | $payload = '{"foo":"bår"}'; 93 | $headers = [ 94 | 'Content-Type' => 'application/json', 95 | 'x-encryption-certificate-fingerprint' => 'some previous value' 96 | ]; 97 | $request = new Request('POST', 'https://api.mastercard.com/service', $headers, $payload); 98 | 99 | // WHEN 100 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 101 | $outRequest = $instanceUnderTest->interceptRequest($request); 102 | 103 | // THEN 104 | $this->assertSame($outRequest, $request); 105 | $encryptedPayload = $request->getBody()->__toString(); 106 | $this->assertFalse(StringUtils::contains($encryptedPayload, 'foo')); 107 | $this->assertTrue(StringUtils::contains($encryptedPayload, 'encryptedFoo')); 108 | $this->assertEquals(1, sizeof($request->getHeader('Content-Length'))); 109 | $this->assertEquals(strval(strlen($encryptedPayload)), $request->getHeader('Content-Length')[0]); 110 | $this->assertEquals(1, sizeof($request->getHeader('x-iv'))); 111 | $this->assertEquals(1, sizeof($request->getHeader('x-encrypted-key'))); 112 | $this->assertEquals('SHA256', $request->getHeader('x-oaep-padding-digest-algorithm')[0]); 113 | $this->assertEquals(1, sizeof($request->getHeader('x-encryption-certificate-fingerprint'))); 114 | $this->assertEquals('80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279', $request->getHeader('x-encryption-certificate-fingerprint')[0]); 115 | $this->assertEquals('761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79', $request->getHeader('x-encryption-key-fingerprint')[0]); 116 | } 117 | 118 | public function testInterceptResponse_ShouldDecryptResponsePayloadAndUpdateContentLengthHeader() 119 | { 120 | 121 | // GIVEN 122 | $encryptedPayload = '{ 123 | "encryptedData": { 124 | "iv": "a32059c51607d0d02e823faecda5fb15", 125 | "encryptedKey": "a31cfe7a7981b72428c013270619554c1d645c04b9d51c7eaf996f55749ef62fd7c7f8d334f95913be41ae38c46d192670fd1acb84ebb85a00cd997f1a9a3f782229c7bf5f0fdf49fe404452d7ed4fd41fbb95b787d25893fbf3d2c75673cecc8799bbe3dd7eb4fe6d3f744b377572cdf8aba1617194e10475b6cd6a8dd4fb8264f8f51534d8f7ac7c10b4ce9c44d15066724b03a0ab0edd512f9e6521fdb5841cd6964e457d6b4a0e45ba4aac4e77d6bbe383d6147e751fa88bc26278bb9690f9ee84b17123b887be2dcef0873f4f9f2c895d90e23456fafb01b99885e31f01a3188f0ad47edf22999cc1d0ddaf49e1407375117b5d66f1f185f2b57078d255", 126 | "encryptedValue": "21d754bdb4567d35d58720c9f8364075", 127 | "oaepHashingAlgorithm": "SHA256" 128 | } 129 | }'; 130 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 131 | ->withDecryptionPath('$.encryptedData', '$.data') 132 | ->build(); 133 | $headers = ['Content-Type' => 'application/json']; 134 | $response = new Response(200, $headers, $encryptedPayload); 135 | 136 | // WHEN 137 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 138 | $outResponse = $instanceUnderTest->interceptResponse($response); 139 | 140 | // THEN 141 | $this->assertSame($outResponse, $response); 142 | $payload = $response->getBody()->__toString(); 143 | $this->assertJsonStringEqualsJsonString('{"data":"string"}', $payload); 144 | $this->assertEquals(1, sizeof($response->getHeader('Content-Length'))); 145 | $this->assertEquals(strval(strlen($payload)), $response->getHeader('Content-Length')[0]); 146 | } 147 | 148 | public function testInterceptResponse_ShouldDoNothing_WhenNoPayload() 149 | { 150 | 151 | // GIVEN 152 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder()->build(); 153 | $response = new Response(200); 154 | 155 | // WHEN 156 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 157 | $outResponse = $instanceUnderTest->interceptResponse($response); 158 | 159 | // THEN 160 | $this->assertSame($outResponse, $response); 161 | $this->assertEmpty($response->getBody()->__toString()); 162 | $this->assertEquals(0, sizeof($response->getHeaders())); 163 | } 164 | 165 | public function testInterceptResponse_ShouldThrowEncryptionException_WhenDecryptionFails() 166 | { 167 | 168 | // GIVEN 169 | $encryptedPayload = '{ 170 | "encryptedData": { 171 | "iv": "a2c494ca28dec4f3d6ce7d68b1044cfe", 172 | "encryptedKey": "NOT A VALID KEY!", 173 | "encryptedValue": "0672589113046bf692265b6ea6088184" 174 | } 175 | }'; 176 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 177 | ->withDecryptionPath('$.encryptedData', '$.data') 178 | ->build(); 179 | $headers = ['Content-Type' => 'application/json']; 180 | $response = new Response(200, $headers, $encryptedPayload); 181 | 182 | // THEN 183 | $this->expectException(EncryptionException::class); 184 | $this->expectExceptionMessage('Failed to decode and unwrap the provided secret key value!'); 185 | 186 | // WHEN 187 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 188 | $instanceUnderTest->interceptResponse($response); 189 | } 190 | 191 | public function testInterceptRequest_ShouldDecryptResponsePayloadAndRemoveEncryptionHttpHeaders_WhenRequestedInConfig() 192 | { 193 | 194 | // GIVEN 195 | $encryptedPayload = '{ 196 | "encryptedData": { 197 | "encryptedValue": "21d754bdb4567d35d58720c9f8364075" 198 | } 199 | }'; 200 | $config = TestUtils::getTestFieldLevelEncryptionConfigBuilder() 201 | ->withDecryptionPath('$.encryptedData', '$.data') 202 | ->withIvHeaderName('x-iv') 203 | ->withEncryptedKeyHeaderName('x-encrypted-key') 204 | ->withOaepPaddingDigestAlgorithmHeaderName('x-oaep-padding-digest-algorithm') 205 | ->withEncryptionCertificateFingerprintHeaderName('x-encryption-certificate-fingerprint') 206 | ->withEncryptionKeyFingerprintHeaderName('x-encryption-key-fingerprint') 207 | ->build(); 208 | $headers = [ 209 | 'content-length' => '100', 210 | 'x-iv' => 'a32059c51607d0d02e823faecda5fb15', 211 | 'x-encrypted-key' => 'a31cfe7a7981b72428c013270619554c1d645c04b9d51c7eaf996f55749ef62fd7c7f8d334f95913be41ae38c46d192670fd1acb84ebb85a00cd997f1a9a3f782229c7bf5f0fdf49fe404452d7ed4fd41fbb95b787d25893fbf3d2c75673cecc8799bbe3dd7eb4fe6d3f744b377572cdf8aba1617194e10475b6cd6a8dd4fb8264f8f51534d8f7ac7c10b4ce9c44d15066724b03a0ab0edd512f9e6521fdb5841cd6964e457d6b4a0e45ba4aac4e77d6bbe383d6147e751fa88bc26278bb9690f9ee84b17123b887be2dcef0873f4f9f2c895d90e23456fafb01b99885e31f01a3188f0ad47edf22999cc1d0ddaf49e1407375117b5d66f1f185f2b57078d255', 212 | 'x-oaep-padding-digest-algorithm' => 'SHA256', 213 | 'x-encryption-key-fingerprint' => '761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79', 214 | 'X-ENCRYPTION-CERTIFICATE-FINGERPRINT' => '80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279' 215 | ]; 216 | $response = new Response(200, $headers, $encryptedPayload); 217 | 218 | // WHEN 219 | $instanceUnderTest = new PsrHttpMessageEncryptionInterceptor($config); 220 | $outResponse = $instanceUnderTest->interceptResponse($response); 221 | 222 | // THEN 223 | $this->assertSame($outResponse, $response); 224 | $payload = $response->getBody()->__toString(); 225 | $this->assertJsonStringEqualsJsonString('{"data":"string"}', $payload); 226 | $this->assertEquals(1, sizeof($response->getHeader('Content-Length'))); 227 | $this->assertEquals(strval(strlen($payload)), $response->getHeader('Content-Length')[0]); 228 | $this->assertEquals(0, sizeof($response->getHeader('x-iv'))); 229 | $this->assertEquals(0, sizeof($response->getHeader('x-encrypted-key'))); 230 | $this->assertEquals(0, sizeof($response->getHeader('x-oaep-padding-digest-algorithm'))); 231 | $this->assertEquals(0, sizeof($response->getHeader('x-encryption-key-fingerprint'))); 232 | $this->assertEquals(0, sizeof($response->getHeader('x-encryption-certificate-fingerprint'))); 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /tests/Developer/Interceptors/PsrHttpMessageJweInterceptorTest.php: -------------------------------------------------------------------------------- 1 | withEncryptionCertificate($encryptionCerificate) 24 | ->withEncryptionPath('$.foo', '$.encryptedFoo') 25 | ->build(); 26 | 27 | $payload = '{"foo":"bår"}'; 28 | $headers = ['Content-Type' => 'application/json']; 29 | $request = new Request('POST', 'https://api.mastercard.com/service', $headers, $payload); 30 | 31 | // WHEN 32 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 33 | $outRequest = $instanceUnderTest->interceptRequest($request); 34 | 35 | // THEN 36 | $this->assertSame($outRequest, $request); 37 | $encryptedPayload = $request->getBody()->__toString(); 38 | $this->assertFalse(StringUtils::contains($encryptedPayload, 'foo')); 39 | $this->assertTrue(StringUtils::contains($encryptedPayload, 'encryptedFoo')); 40 | $this->assertEquals(1, sizeof($request->getHeader('Content-Length'))); 41 | $this->assertEquals(strval(strlen($encryptedPayload)), $request->getHeader('Content-Length')[0]); 42 | } 43 | 44 | public function testInterceptRequest_ShouldDoNothing_WhenNoPayload() 45 | { 46 | $encryptionCerificate = EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 47 | 48 | // GIVEN 49 | $config = JweConfigBuilder::aJweEncryptionConfig() 50 | ->withEncryptionCertificate($encryptionCerificate) 51 | ->withEncryptionPath('$.foo', '$.encryptedFoo') 52 | ->build(); 53 | 54 | $request = new Request('GET', 'https://api.mastercard.com/service'); 55 | $initialHeaderCount = sizeof($request->getHeaders()); 56 | 57 | // WHEN 58 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 59 | $outRequest = $instanceUnderTest->interceptRequest($request); 60 | 61 | // THEN 62 | $this->assertSame($outRequest, $request); 63 | $this->assertEmpty($request->getBody()->__toString()); 64 | $this->assertEquals($initialHeaderCount, sizeof($request->getHeaders())); 65 | } 66 | 67 | public function testInterceptResponse_ShouldDecryptResponsePayloadAndUpdateContentLengthHeader() 68 | { 69 | 70 | // GIVEN 71 | $encryptedPayload = "{" . 72 | "\"encryptedPayload\":\"eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.8c6vxeZOUBS8A9SXYUSrRnfl1ht9xxciB7TAEv84etZhQQ2civQKso-htpa2DWFBSUm-UYlxb6XtXNXZxuWu-A0WXjwi1K5ZAACc8KUoYnqPldEtC9Q2bhbQgc_qZF_GxeKrOZfuXc9oi45xfVysF_db4RZ6VkLvY2YpPeDGEMX_nLEjzqKaDz_2m0Ae_nknr0p_Nu0m5UJgMzZGR4Sk1DJWa9x-WJLEyo4w_nRDThOjHJshOHaOU6qR5rdEAZr_dwqnTHrjX9Qm9N9gflPGMaJNVa4mvpsjz6LJzjaW3nJ2yCoirbaeJyCrful6cCiwMWMaDMuiBDPKa2ovVTy0Sw.w0Nkjxl0T9HHNu4R.suRZaYu6Ui05Z3-vsw.akknMr3Dl4L0VVTGPUszcA\"}"; 73 | 74 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 75 | 76 | $config = JweConfigBuilder::aJweEncryptionConfig() 77 | ->withDecryptionKey($decryptionKey) 78 | ->withDecryptionPath('$.encryptedPayload', '$') 79 | ->build(); 80 | 81 | $headers = ['Content-Type' => 'application/json']; 82 | $response = new Response(200, $headers, $encryptedPayload); 83 | 84 | // WHEN 85 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 86 | $outResponse = $instanceUnderTest->interceptResponse($response); 87 | 88 | // THEN 89 | $this->assertSame($outResponse, $response); 90 | $payload = $response->getBody()->__toString(); 91 | $this->assertJsonStringEqualsJsonString('{"foo":"bar"}', $payload); 92 | $this->assertEquals(1, sizeof($response->getHeader('Content-Length'))); 93 | $this->assertEquals(strval(strlen($payload)), $response->getHeader('Content-Length')[0]); 94 | } 95 | 96 | public function testInterceptResponse_ShouldDecryptWithA128CBC_HS256Encryption() 97 | { 98 | 99 | // GIVEN 100 | $encryptedPayload = "{" . 101 | "\"encryptedPayload\":\"eyJraWQiOiI3NjFiMDAzYzFlYWRlM2E1NDkwZTUwMDBkMzc4ODdiYWE1ZTZlYzBlMjI2YzA3NzA2ZTU5OTQ1MWZjMDMyYTc5IiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.5bsamlChk0HR3Nqg2UPJ2Fw4Y0MvC2pwWzNv84jYGkOXyqp1iwQSgETGaplIa7JyLg1ZWOqwNHEx3N7gsN4nzwAnVgz0eta6SsoQUE9YQ-5jek0COslUkoqIQjlQYJnYur7pqttDibj87fcw13G2agle5fL99j1QgFPjNPYqH88DMv481XGFa8O3VfJhW93m73KD2gvE5GasOPOkFK9wjKXc9lMGSgSArp3Awbc_oS2Cho_SbsvuEQwkhnQc2JKT3IaSWu8yK7edNGwD6OZJLhMJzWJlY30dUt2Eqe1r6kMT0IDRl7jHJnVIr2Qpe56CyeZ9V0aC5RH1mI5dYk4kHg.yI0CS3NdBrz9CCW2jwBSDw.6zr2pOSmAGdlJG0gbH53Eg.UFgf3-P9UjgMocEu7QA_vQ\"}"; 102 | 103 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 104 | 105 | $config = JweConfigBuilder::aJweEncryptionConfig() 106 | ->withDecryptionKey($decryptionKey) 107 | ->withDecryptionPath('$.encryptedPayload', '$.foo') 108 | ->build(); 109 | 110 | $headers = ['Content-Type' => 'application/json']; 111 | $response = new Response(200, $headers, $encryptedPayload); 112 | 113 | // WHEN 114 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 115 | $outResponse = $instanceUnderTest->interceptResponse($response); 116 | 117 | // THEN 118 | $this->assertSame($outResponse, $response); 119 | $payload = $response->getBody()->__toString(); 120 | $this->assertJsonStringEqualsJsonString('{"foo":"bar"}', $payload); 121 | $this->assertEquals(1, sizeof($response->getHeader('Content-Length'))); 122 | $this->assertEquals(strval(strlen($payload)), $response->getHeader('Content-Length')[0]); 123 | } 124 | 125 | public function testInterceptResponse_ShouldDoNothing_WhenNoPayload() 126 | { 127 | // GIVEN 128 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 129 | 130 | $config = JweConfigBuilder::aJweEncryptionConfig() 131 | ->withDecryptionKey($decryptionKey) 132 | ->build(); 133 | $response = new Response(200); 134 | 135 | // WHEN 136 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 137 | $outResponse = $instanceUnderTest->interceptResponse($response); 138 | 139 | // THEN 140 | $this->assertSame($outResponse, $response); 141 | $this->assertEmpty($response->getBody()->__toString()); 142 | $this->assertEquals(0, sizeof($response->getHeaders())); 143 | } 144 | 145 | public function testInterceptResponse_ShouldThrowEncryptionException_WhenDecryptionFails() 146 | { 147 | $this->expectException(EncryptionException::class); 148 | $this->expectExceptionMessage('Invalid payload'); 149 | 150 | // GIVEN 151 | $encryptedPayload = '{ 152 | "encryptedData": "NOT-VALID" 153 | }'; 154 | 155 | $decryptionKey = DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.pem"); 156 | 157 | $config = JweConfigBuilder::aJweEncryptionConfig() 158 | ->withDecryptionKey($decryptionKey) 159 | ->withDecryptionPath("$.encryptedPayload", "$.foo") 160 | ->build(); 161 | 162 | $headers = ['Content-Type' => 'application/json']; 163 | $response = new Response(200, $headers, $encryptedPayload); 164 | 165 | // WHEN 166 | $instanceUnderTest = new PsrHttpMessageJweInterceptor($config); 167 | $instanceUnderTest->interceptResponse($response); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /tests/Developer/Interceptors/PsrStreamInterfaceImplTest.php: -------------------------------------------------------------------------------- 1 | instanceUnderTest = new PsrStreamInterfaceImpl(); 12 | $this->instanceUnderTest->write('content'); 13 | } 14 | 15 | public function testClose() { 16 | $this->instanceUnderTest->close(); 17 | $this->assertEmpty($this->instanceUnderTest->__toString()); 18 | $this->assertEquals(0, $this->instanceUnderTest->getSize()); 19 | } 20 | 21 | public function testDetach() { 22 | $this->instanceUnderTest->detach(); 23 | $this->assertEmpty($this->instanceUnderTest->__toString()); 24 | $this->assertEquals(0, $this->instanceUnderTest->getSize()); 25 | } 26 | 27 | public function testGetSize() { 28 | $this->assertEquals(7, $this->instanceUnderTest->getSize()); 29 | } 30 | 31 | public function testTell() { 32 | $this->assertNull($this->instanceUnderTest->tell()); 33 | } 34 | 35 | public function testEof() { 36 | $this->assertFalse($this->instanceUnderTest->eof()); 37 | } 38 | 39 | public function testIsSeekable() { 40 | $this->assertFalse($this->instanceUnderTest->isSeekable()); 41 | } 42 | 43 | public function testSeek() { 44 | $this->assertNull($this->instanceUnderTest->seek(0)); 45 | } 46 | 47 | public function testRewind() { 48 | $this->assertNull($this->instanceUnderTest->rewind()); 49 | } 50 | 51 | public function testIsWritable() { 52 | $this->assertFalse($this->instanceUnderTest->isWritable()); 53 | } 54 | 55 | public function testWrite() { 56 | $this->instanceUnderTest->write('new'); 57 | $this->assertEquals('new', $this->instanceUnderTest->__toString()); 58 | $this->assertEquals(3, $this->instanceUnderTest->getSize()); 59 | } 60 | 61 | public function testIsReadable() { 62 | $this->assertTrue($this->instanceUnderTest->isReadable()); 63 | } 64 | 65 | public function testRead() { 66 | $this->assertEquals('cont', $this->instanceUnderTest->read(4)); 67 | } 68 | 69 | public function testGetContents() { 70 | $this->assertEquals('content', $this->instanceUnderTest->getContents()); 71 | } 72 | 73 | public function testGetMetadata() { 74 | $this->assertEquals([], $this->instanceUnderTest->getMetadata()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /tests/Developer/Json/JsonPathTest.php: -------------------------------------------------------------------------------- 1 | getConstructor(); 13 | 14 | // WHEN 15 | $isPrivate = $constructor->isPrivate(); 16 | 17 | // THEN 18 | $this->assertTrue($isPrivate); 19 | 20 | // COVERAGE 21 | $constructor->setAccessible(true); 22 | $constructor->invoke($class->newInstanceWithoutConstructor()); 23 | } 24 | 25 | public function testNormalizePath() { 26 | 27 | // GIVEN 28 | $jsonPath0 = '$[\'obj1\']'; 29 | $jsonPath1 = '$[\'obj1\'][\'obj2\']'; 30 | $jsonPath2 = '$'; 31 | $jsonPath3 = 'obj1.obj2'; 32 | $jsonPath4 = '$.obj1.obj2'; 33 | $jsonPath5 = 'obj1'; 34 | 35 | // WHEN 36 | $normalizedJsonPath0 = JsonPath::normalizePath($jsonPath0); 37 | $normalizedJsonPath1 = JsonPath::normalizePath($jsonPath1); 38 | $normalizedJsonPath2 = JsonPath::normalizePath($jsonPath2); 39 | $normalizedJsonPath3 = JsonPath::normalizePath($jsonPath3); 40 | $normalizedJsonPath4 = JsonPath::normalizePath($jsonPath4); 41 | $normalizedJsonPath5 = JsonPath::normalizePath($jsonPath5); 42 | 43 | // THEN 44 | $this->assertEquals($jsonPath0, $normalizedJsonPath0); 45 | $this->assertEquals($jsonPath1, $normalizedJsonPath1); 46 | $this->assertEquals($jsonPath2, $normalizedJsonPath2); 47 | $this->assertEquals($jsonPath1, $normalizedJsonPath3); 48 | $this->assertEquals($jsonPath1, $normalizedJsonPath4); 49 | $this->assertEquals($jsonPath0, $normalizedJsonPath5); 50 | } 51 | 52 | public function testNormalizePath_ShouldThrowInvalidArgumentException_WhenJsonPathEmpty() { 53 | 54 | // GIVEN 55 | $jsonPath = ''; 56 | 57 | // THEN 58 | $this->expectException(\InvalidArgumentException::class); 59 | $this->expectExceptionMessage('JSON path must be a non-empty string!'); 60 | 61 | // WHEN 62 | JsonPath::normalizePath($jsonPath); 63 | } 64 | 65 | public function testNormalizePath_ShouldThrowInvalidArgumentException_WhenJsonPathIsNotAString() { 66 | 67 | // GIVEN 68 | $jsonPath = []; 69 | 70 | // THEN 71 | $this->expectException(\InvalidArgumentException::class); 72 | $this->expectExceptionMessage('JSON path must be a non-empty string!'); 73 | 74 | // WHEN 75 | JsonPath::normalizePath($jsonPath); 76 | } 77 | 78 | public function testGetElementKey() { 79 | 80 | // GIVEN 81 | $jsonPath1 = '$[\'obj0\'][\'obj1\'][\'obj2\']'; 82 | $jsonPath2 = 'obj1.obj2'; 83 | $jsonPath3 = '$.obj1.obj2'; 84 | $jsonPath4 = 'obj2'; 85 | 86 | // WHEN 87 | $jsonElementKey1 = JsonPath::getElementKey($jsonPath1); 88 | $jsonElementKey2 = JsonPath::getElementKey($jsonPath2); 89 | $jsonElementKey3 = JsonPath::getElementKey($jsonPath3); 90 | $jsonElementKey4 = JsonPath::getElementKey($jsonPath4); 91 | 92 | // THEN 93 | $this->assertEquals('obj2', $jsonElementKey1); 94 | $this->assertEquals('obj2', $jsonElementKey2); 95 | $this->assertEquals('obj2', $jsonElementKey3); 96 | $this->assertEquals('obj2', $jsonElementKey4); 97 | } 98 | 99 | public function testGetElementKey_ShouldThrowInvalidArgumentException_WhenPathIsInvalid() { 100 | $this->expectException(\InvalidArgumentException::class); 101 | $this->expectExceptionMessage('Unsupported JSON path: $invalidPath!'); 102 | JsonPath::getElementKey('$invalidPath'); 103 | } 104 | 105 | public function testGetParent() { 106 | 107 | // GIVEN 108 | $jsonPath0 = '$[\'obj1\']'; 109 | $jsonPath1 = '$[\'obj1\'][\'obj2\']'; 110 | $jsonPath2 = '$[\'obj1\'][\'obj2\'][\'obj3\']'; 111 | $jsonPath3 = 'obj1.obj2'; 112 | $jsonPath4 = '$.obj1.obj2'; 113 | $jsonPath5 = 'obj1'; 114 | 115 | // WHEN 116 | $parentJsonPath0 = JsonPath::getParentPath($jsonPath0); 117 | $parentJsonPath1 = JsonPath::getParentPath($jsonPath1); 118 | $parentJsonPath2 = JsonPath::getParentPath($jsonPath2); 119 | $parentJsonPath3 = JsonPath::getParentPath($jsonPath3); 120 | $parentJsonPath4 = JsonPath::getParentPath($jsonPath4); 121 | $parentJsonPath5 = JsonPath::getParentPath($jsonPath5); 122 | 123 | // THEN 124 | $this->assertEquals('$', $parentJsonPath0); 125 | $this->assertEquals($jsonPath0, $parentJsonPath1); 126 | $this->assertEquals($jsonPath1, $parentJsonPath2); 127 | $this->assertEquals($jsonPath0, $parentJsonPath3); 128 | $this->assertEquals($jsonPath0, $parentJsonPath4); 129 | $this->assertEquals('$', $parentJsonPath5); 130 | } 131 | 132 | public function testGetParent_ShouldThrowInvalidArgumentException_WhenPathIsInvalid() { 133 | $this->expectException(\InvalidArgumentException::class); 134 | $this->expectExceptionMessage('Unsupported JSON path: $invalidPath!'); 135 | JsonPath::getParentPath('$invalidPath'); 136 | } 137 | 138 | public function testFind() { 139 | 140 | // GIVEN 141 | $json = '{ 142 | "child1": { 143 | "field1": "value1", 144 | "field2": "value2" 145 | }, 146 | "child2": {}, 147 | "child3": { 148 | "grandchild1": [], 149 | "grandchild2": [ "value1", "value2" ] 150 | } 151 | }'; 152 | $jsonObject = json_decode($json); 153 | 154 | // WHEN 155 | $jsonElement1 = JsonPath::find($jsonObject, '$'); 156 | $jsonElement2 = JsonPath::find($jsonObject, '$.child1'); 157 | $jsonElement3 = JsonPath::find($jsonObject, '$.child2'); 158 | $jsonElement4 = JsonPath::find($jsonObject, '$.child3'); 159 | $jsonElement5 = JsonPath::find($jsonObject, '$.child3.grandchild1'); 160 | $jsonElement6 = JsonPath::find($jsonObject, '$.child3.grandchild2'); 161 | $jsonElement7 = JsonPath::find($jsonObject, '$.not.existing.path'); 162 | $jsonElement8 = JsonPath::find($jsonObject, '$invalidPath'); 163 | 164 | // THEN 165 | $this->assertSame($jsonObject, $jsonElement1); 166 | $this->assertSame($jsonObject->child1, $jsonElement2); 167 | $this->assertSame($jsonObject->child2, $jsonElement3); 168 | $this->assertSame($jsonObject->child3, $jsonElement4); 169 | $this->assertSame($jsonObject->child3->grandchild1, $jsonElement5); 170 | $this->assertSame($jsonObject->child3->grandchild2, $jsonElement6); 171 | $this->assertNull($jsonElement7); 172 | $this->assertNull($jsonElement8); 173 | } 174 | 175 | public function testDelete() { 176 | 177 | // GIVEN 178 | $json = '{ 179 | "child1": { 180 | "field1": "value1", 181 | "field2": "value2" 182 | }, 183 | "child2": {}, 184 | "child3": { 185 | "grandchild1": [], 186 | "grandchild2": [ "value1", "value2" ] 187 | } 188 | }'; 189 | $jsonObject = json_decode($json); 190 | 191 | // WHEN 192 | JsonPath::delete($jsonObject, '$.child3.grandchild2'); 193 | 194 | // THEN 195 | $this->assertNull(JsonPath::find($jsonObject, '$.child3.grandchild2')); 196 | } 197 | 198 | public function testDelete_ShouldDoNothing_WhenElementDoesNotExist() { 199 | 200 | // GIVEN 201 | $json = '{ 202 | "child": {} 203 | }'; 204 | $jsonObject = json_decode($json); 205 | 206 | // WHEN 207 | JsonPath::delete($jsonObject, '$.child3.grandchild2'); 208 | 209 | // THEN 210 | $this->assertEquals(json_decode($json), $jsonObject); 211 | } 212 | 213 | public function testGetParentPath_ShouldThrowInvalidArgumentException_WhenJsonPathEmpty() { 214 | 215 | // GIVEN 216 | $jsonPath = ''; 217 | 218 | // THEN 219 | $this->expectException(\InvalidArgumentException::class); 220 | $this->expectExceptionMessage('JSON path must be a non-empty string!'); 221 | 222 | // WHEN 223 | JsonPath::getParentPath($jsonPath); 224 | } 225 | 226 | public function testGetElementKey_ShouldThrowInvalidArgumentException_WhenJsonPathEmpty() { 227 | 228 | // GIVEN 229 | $jsonPath = ''; 230 | 231 | // THEN 232 | $this->expectException(\InvalidArgumentException::class); 233 | $this->expectExceptionMessage('JSON path must be a non-empty string!'); 234 | 235 | // WHEN 236 | JsonPath::getElementKey($jsonPath); 237 | } 238 | 239 | public function testGetParentPath_ShouldThrowInvalidArgumentException_WhenNoParent() { 240 | 241 | // GIVEN 242 | $jsonPath = '$'; 243 | 244 | // THEN 245 | $this->expectException(\InvalidArgumentException::class); 246 | $this->expectExceptionMessage('Unable to find parent for: $'); 247 | 248 | // WHEN 249 | JsonPath::getParentPath($jsonPath); 250 | } 251 | 252 | public function testGetElementKey_ShouldThrowInvalidArgumentException_WhenNoKey() { 253 | 254 | // GIVEN 255 | $jsonPath = '$'; 256 | 257 | // THEN 258 | $this->expectException(\InvalidArgumentException::class); 259 | $this->expectExceptionMessage('Unable to find object key for: $'); 260 | 261 | 262 | // WHEN 263 | JsonPath::getElementKey($jsonPath); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /tests/Developer/Test/TestUtils.php: -------------------------------------------------------------------------------- 1 | getMethod($functionName); 20 | $method->setAccessible(true); 21 | return $method->invokeArgs(null, $args); 22 | } 23 | 24 | public static function getTestEncryptionCertificate() { 25 | return EncryptionKey::load("./resources/Certificates/test_certificate-2048.pem"); 26 | } 27 | 28 | public static function getTestInvalidEncryptionCertificate() { 29 | return EncryptionKey::create("not a certificate!"); 30 | } 31 | 32 | public static function getTestDecryptionKey() { 33 | return DecryptionKey::load("./resources/Keys/Pkcs8/test_key_pkcs8-2048.der"); 34 | } 35 | 36 | public static function getTestFieldLevelEncryptionConfigBuilder() { 37 | return FieldLevelEncryptionConfigBuilder::aFieldLevelEncryptionConfig() 38 | ->withEncryptionCertificate(self::getTestEncryptionCertificate()) 39 | ->withDecryptionKey(self::getTestDecryptionKey()) 40 | ->withOaepPaddingDigestAlgorithm('SHA-256') 41 | ->withEncryptedValueFieldName('encryptedValue') 42 | ->withEncryptedKeyFieldName('encryptedKey') 43 | ->withIvFieldName('iv') 44 | ->withOaepPaddingDigestAlgorithmFieldName('oaepHashingAlgorithm') 45 | ->withEncryptionCertificateFingerprintFieldName('encryptionCertificateFingerprint') 46 | ->withEncryptionCertificateFingerprint('80810fc13a8319fcf0e2ec322c82a4c304b782cc3ce671176343cfe8160c2279') 47 | ->withEncryptionKeyFingerprintFieldName('encryptionKeyFingerprint') 48 | ->withEncryptionKeyFingerprint('761b003c1eade3a5490e5000d37887baa5e6ec0e226c07706e599451fc032a79') 49 | ->withFieldValueEncoding(FieldValueEncoding::HEX); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Developer/Utils/EncodingUtilsTest.php: -------------------------------------------------------------------------------- 1 | getConstructor(); 14 | 15 | // WHEN 16 | $isPrivate = $constructor->isPrivate(); 17 | 18 | // THEN 19 | $this->assertTrue($isPrivate); 20 | 21 | // COVERAGE 22 | $constructor->setAccessible(true); 23 | $constructor->invoke($class->newInstanceWithoutConstructor()); 24 | } 25 | 26 | public function testHexEncode() { 27 | $this->assertEquals('00', EncodingUtils::hexEncode("\0")); 28 | $this->assertEquals('736f6d652064617461', EncodingUtils::hexEncode('some data')); 29 | $this->assertEquals('', EncodingUtils::hexEncode('')); 30 | } 31 | 32 | public function testHexEncode_ShouldThrowInvalidArgumentException_WhenNullValue() { 33 | $this->expectException(\InvalidArgumentException::class); 34 | $this->expectExceptionMessage('Can\'t hex encode an empty value!'); 35 | EncodingUtils::hexEncode(null); 36 | } 37 | 38 | public function testHexEncode_ShouldKeepLeadingZeros() { 39 | $hex = EncodingUtils::hexEncode((new Hash('sha256'))->hash('WIDDIES')); 40 | $this->assertEquals('000000c71f1bda5b63f5165243e10394bc9ebf62e394ef7c6e049c920ea1b181', $hex); 41 | } 42 | 43 | public function testHexDecode() { 44 | $this->assertEquals("\0", EncodingUtils::hexDecode('00')); 45 | $this->assertEquals('some data', EncodingUtils::hexDecode('736f6d652064617461')); 46 | $this->assertEquals('some data', EncodingUtils::hexDecode('736F6D652064617461')); 47 | $this->assertEquals('', EncodingUtils::hexDecode('')); 48 | } 49 | 50 | public function testHexDecode_ShouldThrowInvalidArgumentException_WhenNotAnHexValue() { 51 | $this->expectException(\InvalidArgumentException::class); 52 | $this->expectExceptionMessage('The provided value is not an hex string!'); 53 | EncodingUtils::hexDecode('not an hex string!'); 54 | } 55 | 56 | public function testHexDecode_ShouldThrowInvalidArgumentException_WhenNullValue() { 57 | $this->expectException(\InvalidArgumentException::class); 58 | $this->expectExceptionMessage('Can\'t hex decode an empty value!'); 59 | EncodingUtils::hexDecode(null); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Developer/Utils/EncryptionUtilsTest.php: -------------------------------------------------------------------------------- 1 | getConstructor(); 13 | 14 | // WHEN 15 | $isPrivate = $constructor->isPrivate(); 16 | 17 | // THEN 18 | $this->assertTrue($isPrivate); 19 | 20 | // COVERAGE 21 | $constructor->setAccessible(true); 22 | $constructor->invoke($class->newInstanceWithoutConstructor()); 23 | } 24 | 25 | public function testLoadEncryptionCertificate() { 26 | // GIVEN 27 | $certificatePath = './resources/Certificates/test_certificate-2048.pem'; 28 | 29 | // WHEN 30 | $certificate = EncryptionUtils::loadEncryptionCertificate($certificatePath); 31 | 32 | // THEN 33 | $this->assertNotEmpty($certificate); 34 | $this->assertNotFalse($certificate); 35 | } 36 | 37 | public function testLoadDecryptionKey() { 38 | // GIVEN 39 | $keyContainerPath = './resources/Keys/Pkcs12/test_key.p12'; 40 | $keyAlias = 'mykeyalias'; 41 | $keyPassword = 'Password1'; 42 | 43 | // WHEN 44 | $privateKey = EncryptionUtils::loadDecryptionKey($keyContainerPath, $keyAlias, $keyPassword); 45 | 46 | // THEN 47 | $this->assertNotEmpty($privateKey); 48 | $this->assertNotFalse($privateKey); 49 | $this->assertEquals($keyAlias, $privateKey->getAlias()); 50 | $this->assertEquals($keyPassword, $privateKey->getPassword()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Developer/Utils/StringUtilsTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(StringUtils::contains('Hello', '')); 10 | $this->assertTrue(StringUtils::contains('Hello', 'el')); 11 | $this->assertFalse(StringUtils::contains('Hello', 'le')); 12 | $this->assertFalse(StringUtils::contains('Hello', '-Hello-')); 13 | } 14 | 15 | public function testStartWith() { 16 | $this->assertTrue(StringUtils::startsWith('Hello', '')); 17 | $this->assertTrue(StringUtils::startsWith('Hello', 'He')); 18 | $this->assertFalse(StringUtils::startsWith('Hello', 'le')); 19 | $this->assertFalse(StringUtils::startsWith('Hello', '-Hello-')); 20 | } 21 | 22 | public function testEndWith() { 23 | $this->assertTrue(StringUtils::endsWith('Hello', '')); 24 | $this->assertTrue(StringUtils::endsWith('Hello', 'lo')); 25 | $this->assertFalse(StringUtils::endsWith('Hello', 'le')); 26 | $this->assertFalse(StringUtils::endsWith('Hello', '-Hello-')); 27 | } 28 | } --------------------------------------------------------------------------------