14
14
namespace Minishlink \WebPush ;
15
15
16
16
use Base64Url \Base64Url ;
17
+ use Brick \Math \BigInteger ;
18
+ use Jose \Component \Core \JWK ;
19
+ use Jose \Component \Core \Util \Ecc \Curve ;
17
20
use Jose \Component \Core \Util \Ecc \NistCurve ;
18
- use Jose \Component \Core \Util \Ecc \Point ;
19
21
use Jose \Component \Core \Util \Ecc \PrivateKey ;
20
- use Jose \Component \Core \Util \Ecc \ PublicKey ;
22
+ use Jose \Component \Core \Util \ECKey ;
21
23
22
24
class Encryption
23
25
{
@@ -82,25 +84,41 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
82
84
$ userPublicKey = Base64Url::decode ($ userPublicKey );
83
85
$ userAuthToken = Base64Url::decode ($ userAuthToken );
84
86
85
- $ curve = NistCurve::curve256 ();
86
-
87
87
// get local key pair
88
- list ($ localPublicKeyObject , $ localPrivateKeyObject ) = $ localKeyObject ;
89
- $ localPublicKey = hex2bin (Utils::serializePublicKey ($ localPublicKeyObject ));
88
+ if (count ($ localKeyObject ) === 1 ) {
89
+ /** @var JWK $localJwk */
90
+ $ localJwk = current ($ localKeyObject );
91
+ $ localPublicKey = hex2bin (Utils::serializePublicKeyFromJWK ($ localJwk ));
92
+ } else {
93
+ /** @var PrivateKey $localPrivateKeyObject */
94
+ list ($ localPublicKeyObject , $ localPrivateKeyObject ) = $ localKeyObject ;
95
+ $ localPublicKey = hex2bin (Utils::serializePublicKey ($ localPublicKeyObject ));
96
+ $ localJwk = new JWK ([
97
+ 'kty ' => 'EC ' ,
98
+ 'crv ' => 'P-256 ' ,
99
+ 'd ' => $ localPrivateKeyObject ->getSecret ()->getX (), // @phpstan-ignore-line
100
+ 'x ' => Base64Url::encode ($ localPublicKeyObject [0 ]),
101
+ 'y ' => Base64Url::encode ($ localPublicKeyObject [1 ]),
102
+ ]);
103
+ }
90
104
if (!$ localPublicKey ) {
91
105
throw new \ErrorException ('Failed to convert local public key from hexadecimal to binary ' );
92
106
}
93
107
94
108
// get user public key object
95
109
[$ userPublicKeyObjectX , $ userPublicKeyObjectY ] = Utils::unserializePublicKey ($ userPublicKey );
96
- $ userPublicKeyObject = $ curve ->getPublicKeyFrom (
97
- gmp_init (bin2hex ($ userPublicKeyObjectX ), 16 ),
98
- gmp_init (bin2hex ($ userPublicKeyObjectY ), 16 )
99
- );
110
+ $ userJwk = new JWK ([
111
+ 'kty ' => 'EC ' ,
112
+ 'crv ' => 'P-256 ' ,
113
+ 'x ' => Base64Url::encode ($ userPublicKeyObjectX ),
114
+ 'y ' => Base64Url::encode ($ userPublicKeyObjectY ),
115
+ ]);
100
116
101
117
// get shared secret from user public key and local private key
102
- $ sharedSecret = $ curve ->mul ($ userPublicKeyObject ->getPoint (), $ localPrivateKeyObject ->getSecret ())->getX ();
103
- $ sharedSecret = hex2bin (str_pad (gmp_strval ($ sharedSecret , 16 ), 64 , '0 ' , STR_PAD_LEFT ));
118
+
119
+ $ sharedSecret = self ::calculateAgreementKey ($ localJwk , $ userJwk );
120
+
121
+ $ sharedSecret = str_pad ($ sharedSecret , 32 , chr (0 ), STR_PAD_LEFT );
104
122
if (!$ sharedSecret ) {
105
123
throw new \ErrorException ('Failed to convert shared secret from hexadecimal to binary ' );
106
124
}
@@ -132,7 +150,7 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
132
150
];
133
151
}
134
152
135
- public static function getContentCodingHeader ($ salt , $ localPublicKey , $ contentEncoding ): string
153
+ public static function getContentCodingHeader (string $ salt , string $ localPublicKey , string $ contentEncoding ): string
136
154
{
137
155
if ($ contentEncoding === "aes128gcm " ) {
138
156
return $ salt
@@ -186,7 +204,7 @@ private static function hkdf(string $salt, string $ikm, string $info, int $lengt
186
204
*
187
205
* @throws \ErrorException
188
206
*/
189
- private static function createContext (string $ clientPublicKey , string $ serverPublicKey , $ contentEncoding ): ?string
207
+ private static function createContext (string $ clientPublicKey , string $ serverPublicKey , string $ contentEncoding ): ?string
190
208
{
191
209
if ($ contentEncoding === "aes128gcm " ) {
192
210
return null ;
@@ -256,9 +274,10 @@ private static function createLocalKeyObjectUsingPurePhpMethod(): array
256
274
{
257
275
$ curve = NistCurve::curve256 ();
258
276
$ privateKey = $ curve ->createPrivateKey ();
277
+ $ publicKey = $ curve ->createPublicKey ($ privateKey );
259
278
260
279
return [
261
- $ curve -> createPublicKey ( $ privateKey ) ,
280
+ $ publicKey ,
262
281
$ privateKey ,
263
282
];
264
283
}
@@ -285,11 +304,13 @@ private static function createLocalKeyObjectUsingOpenSSL(): array
285
304
}
286
305
287
306
return [
288
- new PublicKey (Point::create (
289
- gmp_init (bin2hex ($ details ['ec ' ]['x ' ]), 16 ),
290
- gmp_init (bin2hex ($ details ['ec ' ]['y ' ]), 16 )
291
- )),
292
- PrivateKey::create (gmp_init (bin2hex ($ details ['ec ' ]['d ' ]), 16 ))
307
+ new JWK ([
308
+ 'kty ' => 'EC ' ,
309
+ 'crv ' => 'P-256 ' ,
310
+ 'x ' => Base64Url::encode ($ details ['ec ' ]['x ' ]),
311
+ 'y ' => Base64Url::encode ($ details ['ec ' ]['y ' ]),
312
+ 'd ' => Base64Url::encode ($ details ['ec ' ]['d ' ]),
313
+ ])
293
314
];
294
315
}
295
316
@@ -318,4 +339,64 @@ private static function getIKM(string $userAuthToken, string $userPublicKey, str
318
339
319
340
return $ sharedSecret ;
320
341
}
342
+
343
+ private static function calculateAgreementKey (JWK $ private_key , JWK $ public_key ): string
344
+ {
345
+ if (function_exists ('openssl_pkey_derive ' )) {
346
+ try {
347
+ $ publicPem = ECKey::convertPublicKeyToPEM ($ public_key );
348
+ $ privatePem = ECKey::convertPrivateKeyToPEM ($ private_key );
349
+
350
+ $ result = openssl_pkey_derive ($ publicPem , $ privatePem , 256 ); // @phpstan-ignore-line
351
+ if ($ result === false ) {
352
+ throw new \Exception ('Unable to compute the agreement key ' );
353
+ }
354
+ return $ result ;
355
+ } catch (\Throwable $ throwable ) {
356
+ //Does nothing. Will fallback to the pure PHP function
357
+ }
358
+ }
359
+
360
+
361
+ $ curve = NistCurve::curve256 ();
362
+ try {
363
+ $ rec_x = self ::convertBase64ToBigInteger ($ public_key ->get ('x ' ));
364
+ $ rec_y = self ::convertBase64ToBigInteger ($ public_key ->get ('y ' ));
365
+ $ sen_d = self ::convertBase64ToBigInteger ($ private_key ->get ('d ' ));
366
+ $ priv_key = PrivateKey::create ($ sen_d );
367
+ $ pub_key = $ curve ->getPublicKeyFrom ($ rec_x , $ rec_y );
368
+
369
+ return hex2bin ($ curve ->mul ($ pub_key ->getPoint (), $ priv_key ->getSecret ())->getX ()->toBase (16 )); // @phpstan-ignore-line
370
+ } catch (\Throwable $ e ) {
371
+ $ rec_x = self ::convertBase64ToGMP ($ public_key ->get ('x ' ));
372
+ $ rec_y = self ::convertBase64ToGMP ($ public_key ->get ('y ' ));
373
+ $ sen_d = self ::convertBase64ToGMP ($ private_key ->get ('d ' ));
374
+ $ priv_key = PrivateKey::create ($ sen_d ); // @phpstan-ignore-line
375
+ $ pub_key = $ curve ->getPublicKeyFrom ($ rec_x , $ rec_y ); // @phpstan-ignore-line
376
+
377
+ return hex2bin (gmp_strval ($ curve ->mul ($ pub_key ->getPoint (), $ priv_key ->getSecret ())->getX (), 16 )); // @phpstan-ignore-line
378
+ }
379
+ }
380
+
381
+ /**
382
+ * @param string $value
383
+ * @return BigInteger
384
+ */
385
+ private static function convertBase64ToBigInteger (string $ value ): BigInteger
386
+ {
387
+ $ value = unpack ('H* ' , Base64Url::decode ($ value ));
388
+
389
+ return BigInteger::fromBase ($ value [1 ], 16 );
390
+ }
391
+
392
+ /**
393
+ * @param string $value
394
+ * @return \GMP
395
+ */
396
+ private static function convertBase64ToGMP (string $ value ): \GMP
397
+ {
398
+ $ value = unpack ('H* ' , Base64Url::decode ($ value ));
399
+
400
+ return gmp_init ($ value [1 ], 16 );
401
+ }
321
402
}
0 commit comments