1414namespace Minishlink \WebPush ;
1515
1616use Base64Url \Base64Url ;
17+ use Brick \Math \BigInteger ;
18+ use Jose \Component \Core \JWK ;
19+ use Jose \Component \Core \Util \Ecc \Curve ;
1720use Jose \Component \Core \Util \Ecc \NistCurve ;
18- use Jose \Component \Core \Util \Ecc \Point ;
1921use Jose \Component \Core \Util \Ecc \PrivateKey ;
20- use Jose \Component \Core \Util \Ecc \ PublicKey ;
22+ use Jose \Component \Core \Util \ECKey ;
2123
2224class Encryption
2325{
@@ -82,25 +84,41 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
8284 $ userPublicKey = Base64Url::decode ($ userPublicKey );
8385 $ userAuthToken = Base64Url::decode ($ userAuthToken );
8486
85- $ curve = NistCurve::curve256 ();
86-
8787 // 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+ }
90104 if (!$ localPublicKey ) {
91105 throw new \ErrorException ('Failed to convert local public key from hexadecimal to binary ' );
92106 }
93107
94108 // get user public key object
95109 [$ 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+ ]);
100116
101117 // 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 );
104122 if (!$ sharedSecret ) {
105123 throw new \ErrorException ('Failed to convert shared secret from hexadecimal to binary ' );
106124 }
@@ -132,7 +150,7 @@ public static function deterministicEncrypt(string $payload, string $userPublicK
132150 ];
133151 }
134152
135- public static function getContentCodingHeader ($ salt , $ localPublicKey , $ contentEncoding ): string
153+ public static function getContentCodingHeader (string $ salt , string $ localPublicKey , string $ contentEncoding ): string
136154 {
137155 if ($ contentEncoding === "aes128gcm " ) {
138156 return $ salt
@@ -186,7 +204,7 @@ private static function hkdf(string $salt, string $ikm, string $info, int $lengt
186204 *
187205 * @throws \ErrorException
188206 */
189- private static function createContext (string $ clientPublicKey , string $ serverPublicKey , $ contentEncoding ): ?string
207+ private static function createContext (string $ clientPublicKey , string $ serverPublicKey , string $ contentEncoding ): ?string
190208 {
191209 if ($ contentEncoding === "aes128gcm " ) {
192210 return null ;
@@ -256,9 +274,10 @@ private static function createLocalKeyObjectUsingPurePhpMethod(): array
256274 {
257275 $ curve = NistCurve::curve256 ();
258276 $ privateKey = $ curve ->createPrivateKey ();
277+ $ publicKey = $ curve ->createPublicKey ($ privateKey );
259278
260279 return [
261- $ curve -> createPublicKey ( $ privateKey ) ,
280+ $ publicKey ,
262281 $ privateKey ,
263282 ];
264283 }
@@ -285,11 +304,13 @@ private static function createLocalKeyObjectUsingOpenSSL(): array
285304 }
286305
287306 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+ ])
293314 ];
294315 }
295316
@@ -318,4 +339,64 @@ private static function getIKM(string $userAuthToken, string $userPublicKey, str
318339
319340 return $ sharedSecret ;
320341 }
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+ }
321402}
0 commit comments