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

hyperwallet / php-sdk / 3912820865

pending completion
3912820865

push

github

GitHub
DTPAYETWO-761- Added 'isDefaultTransferMethod' attribute for default accounts(V3) (#125)

12 of 12 new or added lines in 4 files covered. (100.0%)

1821 of 1861 relevant lines covered (97.85%)

18.49 hits per line

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

88.6
/src/Hyperwallet/Util/HyperwalletEncryption.php
1
<?php
2
namespace Hyperwallet\Util;
3
use GuzzleHttp\Client;
4
use GuzzleHttp\Exception\BadResponseException;
5
use GuzzleHttp\Exception\ConnectException;
6
use GuzzleHttp\UriTemplate\UriTemplate;
7
use Hyperwallet\Exception\HyperwalletApiException;
8
use Hyperwallet\Exception\HyperwalletException;
9
use Hyperwallet\Model\BaseModel;
10
use Hyperwallet\Response\ErrorResponse;
11
use Composer\Autoload\ClassLoader;
12
use phpseclib\Crypt\RSA;
13
use phpseclib\Math\BigInteger;
14
use phpseclib\Crypt\Hash;
15
use JOSE_URLSafeBase64;
16
use JOSE_JWS;
17
use JOSE_JWE;
18
use JOSE_JWK;
19
use JOSE_JWT;
20

21
/**
22
 * The encryption service for Hyperwallet client's requests/responses
23
 *
24
 * @package Hyperwallet\Util
25
 */
26
class HyperwalletEncryption {
27

28
    /**
29
     * String that can be a URL or path to file with client JWK set
30
     *
31
     * @var string
32
     */
33
    private $clientPrivateKeySetLocation;
34

35
    /**
36
     * String that can be a URL or path to file with hyperwallet JWK set
37
     *
38
     * @var string
39
     */
40
    private $hyperwalletKeySetLocation;
41

42
    /**
43
     * JWE encryption algorithm, by default value = RSA-OAEP-256
44
     *
45
     * @var string
46
     */
47
    private $encryptionAlgorithm;
48

49
    /**
50
     * JWS signature algorithm, by default value = RS256
51
     *
52
     * @var string
53
     */
54
    private $signAlgorithm;
55

56
    /**
57
     * JWE encryption method, by default value = A256CBC-HS512
58
     *
59
     * @var string
60
     */
61
    private $encryptionMethod;
62

63
    /**
64
     * Minutes when JWS signature is valid, by default value = 5
65
     *
66
     * @var integer
67
     */
68
    private $jwsExpirationMinutes;
69

70
    /**
71
     * JWS key id header param
72
     *
73
     * @var string
74
     */
75
    private $jwsKid;
76

77
    /**
78
     * JWE key id header param
79
     *
80
     * @var string
81
     */
82
    private $jweKid;
83

84
    /**
85
     * Creates a instance of the HyperwalletEncryption
86
     *
87
     * @param string $clientPrivateKeySetLocation String that can be a URL or path to file with client JWK set
88
     * @param string $hyperwalletKeySetLocation String that can be a URL or path to file with hyperwallet JWK set
89
     * @param string $encryptionAlgorithm JWE encryption algorithm, by default value = RSA-OAEP-256
90
     * @param array $signAlgorithm JWS signature algorithm, by default value = RS256
91
     * @param array $encryptionMethod JWE encryption method, by default value = A256CBC-HS512
92
     * @param array $jwsExpirationMinutes Minutes when JWS signature is valid, by default value = 5
93
     */
94
    public function __construct($clientPrivateKeySetLocation, $hyperwalletKeySetLocation,
95
                $encryptionAlgorithm = 'RSA-OAEP-256', $signAlgorithm = 'RS256', $encryptionMethod = 'A256CBC-HS512',
96
                $jwsExpirationMinutes = 5) {
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
        file_put_contents($this->getVendorPath() . "/gree/jose/src/JOSE/JWE.php", file_get_contents(__DIR__ . "/../../JWE"));
13✔
104
    }
13✔
105

106
    /**
107
     * Makes an encrypted request : 1) signs the request body; 2) encrypts payload after signature
108
     *
109
     * @param string $body The request body to be encrypted
110
     * @return string
111
     *
112
     * @throws HyperwalletException
113
     */
114
    public function encrypt($body) {
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
        $privateJweKey = $this->getPrivateJweKey();
6✔
138
        $jwe = JOSE_JWT::decode($body);
6✔
139
        $decryptedBody = $jwe->decrypt($privateJweKey);
6✔
140

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

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

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

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

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

198
    /**
199
     * Retrieves RSA private key by JWK key data
200
     *
201
     * @param array $privateKeyData The JWK key data
202
     * @return RSA
203
     */
204
    private function getPrivateKey($privateKeyData) {
205
        $n = $this->keyParamToBigInteger($privateKeyData['n']);
9✔
206
        $e = $this->keyParamToBigInteger($privateKeyData['e']);
9✔
207
        $d = $this->keyParamToBigInteger($privateKeyData['d']);
9✔
208
        $p = $this->keyParamToBigInteger($privateKeyData['p']);
9✔
209
        $q = $this->keyParamToBigInteger($privateKeyData['q']);
9✔
210
        $qi = $this->keyParamToBigInteger($privateKeyData['qi']);
9✔
211
        $dp = $this->keyParamToBigInteger($privateKeyData['dp']);
9✔
212
        $dq = $this->keyParamToBigInteger($privateKeyData['dq']);
9✔
213
        $primes = array($p, $q);
9✔
214
        $exponents = array($dp, $dq);
9✔
215
        $coefficients = array($qi, $qi);
9✔
216
        array_unshift($primes, "phoney");
9✔
217
        unset($primes[0]);
9✔
218
        array_unshift($exponents, "phoney");
9✔
219
        unset($exponents[0]);
9✔
220
        array_unshift($coefficients, "phoney");
9✔
221
        unset($coefficients[0]);
9✔
222

223
        $pemData = (new RSA())->_convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients);
9✔
224
        $privateKey = new RSA();
9✔
225
        $privateKey->loadKey($pemData);
9✔
226
        if ($privateKeyData['alg'] == 'RSA-OAEP-256') {
9✔
227
            $privateKey->setHash('sha256');
6✔
228
            $privateKey->setMGFHash('sha256');
6✔
229
        }
6✔
230
        return $privateKey;
9✔
231
    }
232

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

243
    /**
244
     * Retrieves RSA public key by JWK key data
245
     *
246
     * @param array $publicKeyData The JWK key data
247
     * @return RSA
248
     */
249
    private function getPublicKey($publicKeyData) {
250
        $publicKeyRaw = new JOSE_JWK($publicKeyData);
8✔
251
        $publicKey = $publicKeyRaw->toKey();
8✔
252
        if ($publicKeyData['alg'] == 'RSA-OAEP-256') {
8✔
253
            $publicKey->setHash('sha256');
8✔
254
            $publicKey->setMGFHash('sha256');
8✔
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
        if (filter_var($keySetLocation, FILTER_VALIDATE_URL) === FALSE) {
10✔
270
            if (!file_exists($keySetLocation)) {
10✔
271
                throw new HyperwalletException("Wrong JWK key set location path = " . $keySetLocation);
1✔
272
            }
273
        }
9✔
274
        return $this->findJwkByAlgorithm(json_decode(file_get_contents($keySetLocation), true), $alg);
9✔
275
    }
276

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

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

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

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

354
    /**
355
     * Finds the path of composer vendor directory
356
     *
357
     * @return string
358
     *
359
     * @throws HyperwalletException
360
     */
361
    public function getVendorPath() {
362
        $reflector = new \ReflectionClass(ClassLoader::class);
13✔
363
        $vendorPath = preg_replace('/^(.*)\/composer\/ClassLoader\.php$/', '$1', $reflector->getFileName() );
13✔
364
        if($vendorPath && is_dir($vendorPath)) {
13✔
365
            return $vendorPath . '/';
13✔
366
        }
367
        throw new HyperwalletException('Failed to find a vendor path');
×
368
    }
369
}
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