• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hyperwallet / php-sdk / 9985723843

18 Jul 2024 04:52AM UTC coverage: 89.764% (-8.1%) from 97.851%
9985723843

Pull #133

github

web-flow
Merge acf57c6cc into 90ebb0292
Pull Request #133: Feature/test phpseclib 3

126 of 267 new or added lines in 6 files covered. (47.19%)

2 existing lines in 1 file now uncovered.

2052 of 2286 relevant lines covered (89.76%)

15.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.0
/src/Hyperwallet/Util/HyperwalletEncryption.php
1
<?php
2

3
namespace Hyperwallet\Util;
4

5
use Hyperwallet\Exception\HyperwalletException;
6
use Composer\Autoload\ClassLoader;
7
use phpseclib3\Crypt\RSA;
8
use phpseclib3\Math\BigInteger;
9
use Services\Jose\URLSafeBase64;
10
use Services\Jose\JOSE_JWS;
11
use Services\Jose\JOSE_JWE;
12
use Services\Jose\JOSE_JWK;
13
use Services\Jose\JOSE_JWT;
14

15
/**
16
 * The encryption service for Hyperwallet client's requests/responses
17
 *
18
 * @package Hyperwallet\Util
19
 */
20
class HyperwalletEncryption
21
{
22

23
    /**
24
     * String that can be a URL or path to file with client JWK set
25
     *
26
     * @var string
27
     */
28
    private $clientPrivateKeySetLocation;
29

30
    /**
31
     * String that can be a URL or path to file with hyperwallet JWK set
32
     *
33
     * @var string
34
     */
35
    private $hyperwalletKeySetLocation;
36

37
    /**
38
     * JWE encryption algorithm, by default value = RSA-OAEP-256
39
     *
40
     * @var string
41
     */
42
    private $encryptionAlgorithm;
43

44
    /**
45
     * JWS signature algorithm, by default value = RS256
46
     *
47
     * @var string
48
     */
49
    private $signAlgorithm;
50

51
    /**
52
     * JWE encryption method, by default value = A256CBC-HS512
53
     *
54
     * @var string
55
     */
56
    private $encryptionMethod;
57

58
    /**
59
     * Minutes when JWS signature is valid, by default value = 5
60
     *
61
     * @var integer
62
     */
63
    private $jwsExpirationMinutes;
64

65
    /**
66
     * JWS key id header param
67
     *
68
     * @var string
69
     */
70
    private $jwsKid;
71

72
    /**
73
     * JWE key id header param
74
     *
75
     * @var string
76
     */
77
    private $jweKid;
78

79
    /**
80
     * Creates a instance of the HyperwalletEncryption
81
     *
82
     * @param string $clientPrivateKeySetLocation String that can be a URL or path to file with client JWK set
83
     * @param string $hyperwalletKeySetLocation String that can be a URL or path to file with hyperwallet JWK set
84
     * @param string $encryptionAlgorithm JWE encryption algorithm, by default value = RSA-OAEP-256
85
     * @param array $signAlgorithm JWS signature algorithm, by default value = RS256
86
     * @param array $encryptionMethod JWE encryption method, by default value = A256CBC-HS512
87
     * @param array $jwsExpirationMinutes Minutes when JWS signature is valid, by default value = 5
88
     */
89
    public function __construct(
90
        $clientPrivateKeySetLocation,
91
        $hyperwalletKeySetLocation,
92
        $encryptionAlgorithm = 'RSA-OAEP-256',
93
        $signAlgorithm = 'RS256',
94
        $encryptionMethod = 'A256CBC-HS512',
95
        $jwsExpirationMinutes = 5
96
    ) {
97
        $this->clientPrivateKeySetLocation = $clientPrivateKeySetLocation;
13✔
98
        $this->hyperwalletKeySetLocation = $hyperwalletKeySetLocation;
13✔
99
        $this->encryptionAlgorithm = $encryptionAlgorithm;
13✔
100
        $this->signAlgorithm = $signAlgorithm;
13✔
101
        $this->encryptionMethod = $encryptionMethod;
13✔
102
        $this->jwsExpirationMinutes = $jwsExpirationMinutes;
13✔
103
    }
13✔
104

105
    /**
106
     * Makes an encrypted request : 1) signs the request body; 2) encrypts payload after signature
107
     *
108
     * @param string $body The request body to be encrypted
109
     * @return string
110
     *
111
     * @throws HyperwalletException
112
     */
113
    public function encrypt($body)
114
    {
115
        $privateJwsKey = $this->getPrivateJwsKey();
10✔
116
        $jws = new JOSE_JWS(new JOSE_JWT($body));
9✔
117
        $jws->header['exp'] = $this->getSignatureExpirationTime();
9✔
118
        $jws->header['kid'] = $this->jwsKid;
9✔
119
        $jws->sign($privateJwsKey, $this->signAlgorithm);
9✔
120

121
        $publicJweKey = $this->getPublicJweKey();
9✔
122
        $jwe = new JOSE_JWE($jws);
8✔
123
        $jwe->header['kid'] = $this->jweKid;
8✔
124
        $jwe->encrypt($publicJweKey, $this->encryptionAlgorithm, $this->encryptionMethod);
8✔
125
        return $jwe->toString();
8✔
126
    }
127

128
    /**
129
     * Decrypts encrypted response : 1) decrypts the request body; 2) verifies the payload signature
130
     *
131
     * @param string $body The response body to be decrypted
132
     * @return string
133
     *
134
     * @throws HyperwalletException
135
     */
136
    public function decrypt($body)
137
    {
138
        $privateJweKey = $this->getPrivateJweKey();
6✔
139
        $jwe = JOSE_JWT::decode($body);
6✔
140
        $decryptedBody = $jwe->decrypt($privateJweKey);
6✔
141

142
        $publicJwsKey = $this->getPublicJwsKey();
5✔
143
        $jwsToVerify = JOSE_JWT::decode($decryptedBody->plain_text);
5✔
144
        $this->checkJwsExpiration($jwsToVerify->header);
5✔
145
        $jwsVerificationResult = $jwsToVerify->verify($publicJwsKey, $this->signAlgorithm);
5✔
146
        return $jwsVerificationResult->claims;
4✔
147
    }
148

149
    /**
150
     * Retrieves JWS RSA private key with algorithm = $this->signAlgorithm
151
     *
152
     * @return RSA
153
     *
154
     * @throws HyperwalletException
155
     */
156
    private function getPrivateJwsKey()
157
    {
158
        $privateKeyData = $this->getJwk($this->clientPrivateKeySetLocation, $this->signAlgorithm);
10✔
159
        $this->jwsKid = $privateKeyData['kid'];
9✔
160
        return $this->getPrivateKey($privateKeyData);
9✔
161
    }
162

163
    /**
164
     * Retrieves JWE RSA public key with algorithm = $this->encryptionAlgorithm
165
     *
166
     * @return RSA
167
     *
168
     * @throws HyperwalletException
169
     */
170
    private function getPublicJweKey()
171
    {
172
        $publicKeyData = $this->getJwk($this->hyperwalletKeySetLocation, $this->encryptionAlgorithm);
9✔
173
        $this->jweKid = $publicKeyData['kid'];
8✔
174
        return $this->getPublicKey($this->convertPrivateKeyToPublic($publicKeyData));
8✔
175
    }
176

177
    /**
178
     * Retrieves JWE RSA private key with algorithm = $this->encryptionAlgorithm
179
     *
180
     * @return RSA
181
     *
182
     * @throws HyperwalletException
183
     */
184
    private function getPrivateJweKey()
185
    {
186
        $privateKeyData = $this->getJwk($this->clientPrivateKeySetLocation, $this->encryptionAlgorithm);
6✔
187
        return $this->getPrivateKey($privateKeyData);
6✔
188
    }
189

190
    /**
191
     * Retrieves JWS RSA public key with algorithm = $this->signAlgorithm
192
     *
193
     * @return RSA
194
     *
195
     * @throws HyperwalletException
196
     */
197
    private function getPublicJwsKey()
198
    {
199
        $publicKeyData = $this->getJwk($this->hyperwalletKeySetLocation, $this->signAlgorithm);
5✔
200
        return $this->getPublicKey($this->convertPrivateKeyToPublic($publicKeyData));
5✔
201
    }
202

203
    /**
204
     * Retrieves RSA private key by JWK key data
205
     *
206
     * @param array $privateKeyData The JWK key data
207
     * @return RSA
208
     */
209
    private function getPrivateKey($privateKeyData)
210
    {
211
        $pemData = RSA::load([
9✔
212
            'e' => $this->keyParamToBigInteger($privateKeyData['e']),
9✔
213
            'n' => $this->keyParamToBigInteger($privateKeyData['n']),
9✔
214
            'd' => $this->keyParamToBigInteger($privateKeyData['d']),
9✔
215
            'p' => $this->keyParamToBigInteger($privateKeyData['p']),
9✔
216
            'q' => $this->keyParamToBigInteger($privateKeyData['q']),
9✔
217
            'dp' => $this->keyParamToBigInteger($privateKeyData['dp']),
9✔
218
            'dq' => $this->keyParamToBigInteger($privateKeyData['dq']),
9✔
219
            'qi' => $this->keyParamToBigInteger($privateKeyData['qi']),
9✔
220
        ]);
9✔
221

222
        $privateKey = RSA::loadPrivateKey($pemData->toString('PKCS1'));
9✔
223

224
        if ($privateKeyData['alg'] == 'RSA-OAEP-256') {
9✔
225
//            $privateKey->setHash('sha256');
226
//            $privateKey->setMGFHash('sha256');
227
        }
6✔
228
        return $privateKey;
9✔
229
    }
230

231
    /**
232
     * Converts base 64 encoded string to BigInteger
233
     *
234
     * @param string $param base 64 encoded string
235
     * @return BigInteger
236
     */
237
    private function keyParamToBigInteger($param)
238
    {
239
        return new BigInteger('0x' . bin2hex(URLSafeBase64::decode($param)), 16);
9✔
240
    }
241

242
    /**
243
     * Retrieves RSA public key by JWK key data
244
     *
245
     * @param array $publicKeyData The JWK key data
246
     * @return RSA
247
     */
248
    private function getPublicKey($publicKeyData)
249
    {
250
        $publicKeyRaw = new JOSE_JWK($publicKeyData);
8✔
251
        $publicKey = $publicKeyRaw->toKey();
8✔
252
        if ($publicKeyData['alg'] == 'RSA-OAEP-256') {
8✔
253
//            $publicKey->setHash('sha256');
254
//            $publicKey->setMGFHash('sha256');
255
        }
8✔
256
        return $publicKey;
8✔
257
    }
258

259
    /**
260
     * Retrieves JWK key by JWK key set location and algorithm
261
     *
262
     * @param string $keySetLocation The location(URL or path to file) of JWK key set
263
     * @param string $alg The target algorithm
264
     * @return array
265
     *
266
     * @throws HyperwalletException
267
     */
268
    private function getJwk($keySetLocation, $alg)
269
    {
270
        if (filter_var($keySetLocation, FILTER_VALIDATE_URL) === false) {
10✔
271
            if (!file_exists($keySetLocation)) {
10✔
272
                throw new HyperwalletException("Wrong JWK key set location path = " . $keySetLocation);
1✔
273
            }
274
        }
9✔
275
        return $this->findJwkByAlgorithm(json_decode(file_get_contents($keySetLocation), true), $alg);
9✔
276
    }
277

278
    /**
279
     * Retrieves JWK key from JWK key set by given algorithm
280
     *
281
     * @param string $jwkSetArray JWK key set
282
     * @param string $alg The target algorithm
283
     * @return array
284
     *
285
     * @throws HyperwalletException
286
     */
287
    private function findJwkByAlgorithm($jwkSetArray, $alg)
288
    {
289
        foreach ($jwkSetArray['keys'] as $jwk) {
9✔
290
            if ($alg == $jwk['alg']) {
9✔
291
                return $jwk;
9✔
292
            }
293
        }
9✔
294
        throw new HyperwalletException("JWK set doesn't contain key with algorithm = " . $alg);
1✔
295
    }
296

297
    /**
298
     * Converts private key to public
299
     *
300
     * @param string $jwk JWK key
301
     * @return array
302
     */
303
    private function convertPrivateKeyToPublic($jwk)
304
    {
305
        if (isset($jwk['d'])) {
8✔
306
            unset($jwk['d']);
×
307
        }
×
308
        if (isset($jwk['p'])) {
8✔
309
            unset($jwk['p']);
×
310
        }
×
311
        if (isset($jwk['q'])) {
8✔
312
            unset($jwk['q']);
×
313
        }
×
314
        if (isset($jwk['qi'])) {
8✔
315
            unset($jwk['qi']);
×
316
        }
×
317
        if (isset($jwk['dp'])) {
8✔
318
            unset($jwk['dp']);
×
319
        }
×
320
        if (isset($jwk['dq'])) {
8✔
321
            unset($jwk['dq']);
×
322
        }
×
323
        return $jwk;
8✔
324
    }
325

326
    /**
327
     * Calculates JWS expiration time in seconds
328
     *
329
     * @return integer
330
     */
331
    private function getSignatureExpirationTime()
332
    {
333
        date_default_timezone_set("UTC");
9✔
334
        $secondsInMinute = 60;
9✔
335
        return time() + $this->jwsExpirationMinutes * $secondsInMinute;
9✔
336
    }
337

338
    /**
339
     * Checks if header 'exp' param has not expired value
340
     *
341
     * @param array $header JWS header array
342
     *
343
     * @throws HyperwalletException
344
     */
345
    public function checkJwsExpiration($header)
346
    {
347
        if (!isset($header['exp'])) {
8✔
348
            throw new HyperwalletException('While trying to verify JWS signature no [exp] header is found');
1✔
349
        }
350
        $exp = $header['exp'];
7✔
351
        if (!is_numeric($exp)) {
7✔
352
            throw new HyperwalletException('Wrong value in [exp] header of JWS signature, must be integer');
1✔
353
        }
354
        if ((int)time() > (int)$exp) {
6✔
355
            throw new HyperwalletException('JWS signature has expired, checked by [exp] JWS header');
1✔
356
        }
357
    }
5✔
358

359
    /**
360
     * Finds the path of composer vendor directory
361
     *
362
     * @return string
363
     *
364
     * @throws HyperwalletException
365
     */
366
    public function getVendorPath()
367
    {
UNCOV
368
        $reflector = new \ReflectionClass(ClassLoader::class);
×
NEW
369
        $vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName());
×
NEW
370
        if ($vendorPath && is_dir($vendorPath)) {
×
UNCOV
371
            return $vendorPath . '/';
×
372
        }
373
        throw new HyperwalletException('Failed to find a vendor path');
×
374
    }
375
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc