Spomky-Labs /
jose
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | /* |
||
| 4 | * The MIT License (MIT) |
||
| 5 | * |
||
| 6 | * Copyright (c) 2014-2016 Spomky-Labs |
||
| 7 | * |
||
| 8 | * This software may be modified and distributed under the terms |
||
| 9 | * of the MIT license. See the LICENSE file for details. |
||
| 10 | */ |
||
| 11 | |||
| 12 | namespace Jose; |
||
| 13 | |||
| 14 | use Assert\Assertion; |
||
| 15 | use Base64Url\Base64Url; |
||
| 16 | |||
| 17 | final class Encrypter implements EncrypterInterface |
||
| 18 | { |
||
| 19 | use Behaviour\HasKeyChecker; |
||
| 20 | use Behaviour\HasJWAManager; |
||
| 21 | use Behaviour\HasCompressionManager; |
||
| 22 | use Behaviour\CommonCipheringMethods; |
||
| 23 | use Behaviour\EncrypterTrait; |
||
| 24 | |||
| 25 | /** |
||
| 26 | * {@inheritdoc} |
||
| 27 | */ |
||
| 28 | public static function createEncrypter(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods = ['DEF', 'ZLIB', 'GZ']) |
||
| 29 | { |
||
| 30 | $encrypter = new self($key_encryption_algorithms, $content_encryption_algorithms, $compression_methods); |
||
| 31 | |||
| 32 | return $encrypter; |
||
| 33 | } |
||
| 34 | |||
| 35 | /** |
||
| 36 | * Decrypter constructor. |
||
| 37 | * |
||
| 38 | * @param string[]|\Jose\Algorithm\KeyEncryptionAlgorithmInterface[] $key_encryption_algorithms |
||
| 39 | * @param string[]|\Jose\Algorithm\ContentEncryptionAlgorithmInterface[] $content_encryption_algorithms |
||
| 40 | * @param string[]|\Jose\Compression\CompressionInterface[] $compression_methods |
||
| 41 | */ |
||
| 42 | public function __construct(array $key_encryption_algorithms, array $content_encryption_algorithms, array $compression_methods) |
||
| 43 | { |
||
| 44 | $this->setKeyEncryptionAlgorithms($key_encryption_algorithms); |
||
| 45 | $this->setContentEncryptionAlgorithms($content_encryption_algorithms); |
||
| 46 | $this->setCompressionMethods($compression_methods); |
||
| 47 | $this->setJWAManager(Factory\AlgorithmManagerFactory::createAlgorithmManager(array_merge($key_encryption_algorithms, $content_encryption_algorithms))); |
||
| 48 | $this->setCompressionManager(Factory\CompressionManagerFactory::createCompressionManager($compression_methods)); |
||
| 49 | } |
||
| 50 | |||
| 51 | /** |
||
| 52 | * {@inheritdoc} |
||
| 53 | */ |
||
| 54 | public function encrypt(Object\JWEInterface &$jwe) |
||
| 55 | { |
||
| 56 | Assertion::false($jwe->isEncrypted(), 'The JWE is already encrypted.'); |
||
| 57 | Assertion::greaterThan($jwe->countRecipients(), 0, 'The JWE does not contain recipient.'); |
||
| 58 | $additional_headers = []; |
||
| 59 | $nb_recipients = $jwe->countRecipients(); |
||
| 60 | $this->prepareEncryptionProcess($jwe, $content_encryption_algorithm, $compression_method, $key_management_mode, $cek, $additional_headers); |
||
| 61 | |||
| 62 | for ($i = 0; $i < $nb_recipients; $i++) { |
||
| 63 | $this->processRecipient($jwe, $jwe->getRecipient($i), $cek, $content_encryption_algorithm, $additional_headers); |
||
| 64 | } |
||
| 65 | |||
| 66 | if (!empty($additional_headers) && 1 === $jwe->countRecipients()) { |
||
| 67 | $jwe = $jwe->withSharedProtectedHeaders(array_merge($jwe->getSharedProtectedHeaders(), $additional_headers)); |
||
| 68 | } |
||
| 69 | |||
| 70 | $iv_size = $content_encryption_algorithm->getIVSize(); |
||
|
0 ignored issues
–
show
|
|||
| 71 | $iv = $this->createIV($iv_size); |
||
| 72 | |||
| 73 | $this->encryptJWE($jwe, $content_encryption_algorithm, $cek, $iv, $compression_method); |
||
| 74 | } |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @param \Jose\Object\JWEInterface $jwe |
||
| 78 | * @param \Jose\Object\RecipientInterface $recipient |
||
| 79 | * @param string $cek |
||
| 80 | * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
||
| 81 | * @param array $additional_headers |
||
| 82 | */ |
||
| 83 | private function processRecipient(Object\JWEInterface $jwe, Object\RecipientInterface &$recipient, $cek, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers) |
||
| 84 | { |
||
| 85 | if (null === $recipient->getRecipientKey()) { |
||
| 86 | return; |
||
| 87 | } |
||
| 88 | $complete_headers = array_merge($jwe->getSharedProtectedHeaders(), $jwe->getSharedHeaders(), $recipient->getHeaders()); |
||
| 89 | $key_encryption_algorithm = $this->findKeyEncryptionAlgorithm($complete_headers); |
||
| 90 | $this->checkKeys($key_encryption_algorithm, $content_encryption_algorithm, $recipient->getRecipientKey()); |
||
| 91 | $encrypted_content_encryption_key = $this->getEncryptedKey($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient->getRecipientKey()); |
||
| 92 | $recipient_headers = $recipient->getHeaders(); |
||
| 93 | if (!empty($additional_headers) && 1 !== $jwe->countRecipients()) { |
||
| 94 | $recipient_headers = array_merge($recipient_headers, $additional_headers); |
||
| 95 | $additional_headers = []; |
||
| 96 | } |
||
| 97 | |||
| 98 | $recipient = Object\Recipient::createRecipientFromLoadedJWE($recipient_headers, $encrypted_content_encryption_key); |
||
| 99 | } |
||
| 100 | |||
| 101 | /** |
||
| 102 | * @param \Jose\Object\JWEInterface $jwe |
||
| 103 | * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
||
| 104 | * @param string $cek |
||
| 105 | * @param string $iv |
||
| 106 | * @param \Jose\Compression\CompressionInterface|null $compression_method |
||
| 107 | */ |
||
| 108 | private function encryptJWE(Object\JWEInterface &$jwe, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, $cek, $iv, Compression\CompressionInterface $compression_method = null) |
||
| 109 | { |
||
| 110 | if (!empty($jwe->getSharedProtectedHeaders())) { |
||
| 111 | $jwe = $jwe->withEncodedSharedProtectedHeaders(Base64Url::encode(json_encode($jwe->getSharedProtectedHeaders()))); |
||
| 112 | } |
||
| 113 | |||
| 114 | $tag = null; |
||
| 115 | $payload = $this->preparePayload($jwe->getPayload(), $compression_method); |
||
| 116 | $aad = null === $jwe->getAAD() ? null : Base64Url::encode($jwe->getAAD()); |
||
| 117 | $ciphertext = $content_encryption_algorithm->encryptContent($payload, $cek, $iv, $aad, $jwe->getEncodedSharedProtectedHeaders(), $tag); |
||
| 118 | $jwe = $jwe->withCiphertext($ciphertext); |
||
| 119 | $jwe = $jwe->withIV($iv); |
||
| 120 | |||
| 121 | if (null !== $tag) { |
||
| 122 | $jwe = $jwe->withTag($tag); |
||
| 123 | } |
||
| 124 | } |
||
| 125 | |||
| 126 | /** |
||
| 127 | * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
||
| 128 | * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
||
| 129 | * @param \Jose\Object\JWKInterface $recipient_key |
||
| 130 | */ |
||
| 131 | private function checkKeys(Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, Object\JWKInterface $recipient_key) |
||
| 132 | { |
||
| 133 | $this->checkKeyUsage($recipient_key, 'encryption'); |
||
| 134 | if ('dir' !== $key_encryption_algorithm->getAlgorithmName()) { |
||
| 135 | $this->checkKeyAlgorithm($recipient_key, $key_encryption_algorithm->getAlgorithmName()); |
||
| 136 | } else { |
||
| 137 | $this->checkKeyAlgorithm($recipient_key, $content_encryption_algorithm->getAlgorithmName()); |
||
| 138 | } |
||
| 139 | } |
||
| 140 | |||
| 141 | /** |
||
| 142 | * @param string $payload |
||
| 143 | * @param \Jose\Compression\CompressionInterface|null $compression_method |
||
| 144 | * |
||
| 145 | * @return string |
||
| 146 | */ |
||
| 147 | private function preparePayload($payload, Compression\CompressionInterface $compression_method = null) |
||
| 148 | { |
||
| 149 | $prepared = is_string($payload) ? $payload : json_encode($payload); |
||
| 150 | Assertion::notNull($prepared, 'The payload is empty or cannot encoded into JSON.'); |
||
| 151 | |||
| 152 | if (null === $compression_method) { |
||
| 153 | return $prepared; |
||
| 154 | } |
||
| 155 | $compressed_payload = $compression_method->compress($prepared); |
||
| 156 | Assertion::string($compressed_payload, 'Compression failed.'); |
||
| 157 | |||
| 158 | return $compressed_payload; |
||
| 159 | } |
||
| 160 | |||
| 161 | /** |
||
| 162 | * @param array $complete_headers |
||
| 163 | * @param string $cek |
||
| 164 | * @param \Jose\Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm |
||
| 165 | * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
||
| 166 | * @param \Jose\Object\JWKInterface $recipient_key |
||
| 167 | * @param array $additional_headers |
||
| 168 | * |
||
| 169 | * @return string|null |
||
| 170 | */ |
||
| 171 | private function getEncryptedKey(array $complete_headers, $cek, Algorithm\KeyEncryptionAlgorithmInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key) |
||
| 172 | { |
||
| 173 | if ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyEncryptionInterface) { |
||
| 174 | return $this->getEncryptedKeyFromKeyEncryptionAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
||
| 175 | } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyWrappingInterface) { |
||
| 176 | return $this->getEncryptedKeyFromKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $recipient_key, $additional_headers); |
||
| 177 | } elseif ($key_encryption_algorithm instanceof Algorithm\KeyEncryption\KeyAgreementWrappingInterface) { |
||
| 178 | return $this->getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm($complete_headers, $cek, $key_encryption_algorithm, $content_encryption_algorithm, $additional_headers, $recipient_key); |
||
| 179 | } |
||
| 180 | } |
||
| 181 | |||
| 182 | /** |
||
| 183 | * @param array $complete_headers |
||
| 184 | * @param string $cek |
||
| 185 | * @param \Jose\Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm |
||
| 186 | * @param \Jose\Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm |
||
| 187 | * @param array $additional_headers |
||
| 188 | * @param \Jose\Object\JWKInterface $recipient_key |
||
| 189 | * |
||
| 190 | * @return string |
||
| 191 | */ |
||
| 192 | private function getEncryptedKeyFromKeyAgreementAndKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyAgreementWrappingInterface $key_encryption_algorithm, Algorithm\ContentEncryptionAlgorithmInterface $content_encryption_algorithm, array &$additional_headers, Object\JWKInterface $recipient_key) |
||
| 193 | { |
||
| 194 | $jwt_cek = $key_encryption_algorithm->wrapAgreementKey($recipient_key, $cek, $content_encryption_algorithm->getCEKSize(), $complete_headers, $additional_headers); |
||
| 195 | |||
| 196 | return $jwt_cek; |
||
| 197 | } |
||
| 198 | |||
| 199 | /** |
||
| 200 | * @param array $complete_headers |
||
| 201 | * @param string $cek |
||
| 202 | * @param \Jose\Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm |
||
| 203 | * @param \Jose\Object\JWKInterface $recipient_key |
||
| 204 | * @param array $additional_headers |
||
| 205 | * |
||
| 206 | * @return string |
||
| 207 | */ |
||
| 208 | private function getEncryptedKeyFromKeyEncryptionAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyEncryptionInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, array &$additional_headers) |
||
| 209 | { |
||
| 210 | return $key_encryption_algorithm->encryptKey($recipient_key, $cek, $complete_headers, $additional_headers); |
||
| 211 | } |
||
| 212 | |||
| 213 | /** |
||
| 214 | * @param array $complete_headers |
||
| 215 | * @param string $cek |
||
| 216 | * @param \Jose\Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm |
||
| 217 | * @param \Jose\Object\JWKInterface $recipient_key |
||
| 218 | * @param array $additional_headers |
||
| 219 | * |
||
| 220 | * @return string |
||
| 221 | */ |
||
| 222 | private function getEncryptedKeyFromKeyWrappingAlgorithm(array $complete_headers, $cek, Algorithm\KeyEncryption\KeyWrappingInterface $key_encryption_algorithm, Object\JWKInterface $recipient_key, &$additional_headers) |
||
| 223 | { |
||
| 224 | return $key_encryption_algorithm->wrapKey($recipient_key, $cek, $complete_headers, $additional_headers); |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * @param array $complete_headers |
||
| 229 | * |
||
| 230 | * @return \Jose\Algorithm\KeyEncryptionAlgorithmInterface |
||
| 231 | */ |
||
| 232 | private function findKeyEncryptionAlgorithm(array $complete_headers) |
||
| 233 | { |
||
| 234 | Assertion::keyExists($complete_headers, 'alg', 'Parameter "alg" is missing.'); |
||
| 235 | $key_encryption_algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']); |
||
| 236 | Assertion::isInstanceOf($key_encryption_algorithm, Algorithm\KeyEncryptionAlgorithmInterface::class, sprintf('The key encryption algorithm "%s" is not supported or not a key encryption algorithm instance.', $complete_headers['alg'])); |
||
| 237 | |||
| 238 | return $key_encryption_algorithm; |
||
| 239 | } |
||
| 240 | |||
| 241 | /** |
||
| 242 | * @param int $size |
||
| 243 | * |
||
| 244 | * @return string |
||
| 245 | */ |
||
| 246 | private function createIV($size) |
||
| 247 | { |
||
| 248 | return random_bytes($size / 8); |
||
| 249 | } |
||
| 250 | } |
||
| 251 |
Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.