1010use OpenSSLCertificate ;
1111use ParagonIE \Sodium \Core \Ed25519 ;
1212use RuntimeException ;
13+ use SpomkyLabs \Pki \ASN1 \Type \Constructed \Sequence ;
14+ use SpomkyLabs \Pki \ASN1 \Type \UnspecifiedType ;
1315use SpomkyLabs \Pki \CryptoEncoding \PEM ;
1416use SpomkyLabs \Pki \CryptoTypes \AlgorithmIdentifier \AlgorithmIdentifier ;
1517use SpomkyLabs \Pki \CryptoTypes \Asymmetric \PrivateKey ;
@@ -228,52 +230,155 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a
228230 }
229231
230232 return match ($ details ['type ' ]) {
231- OPENSSL_KEYTYPE_EC => self ::tryToLoadECKey ($ pem ),
233+ OPENSSL_KEYTYPE_EC => self ::tryToLoadECKey ($ details , $ pem ),
232234 OPENSSL_KEYTYPE_RSA => RSAKey::createFromPEM ($ pem )->toArray (),
233- -1 => self ::tryToLoadOtherKeyTypes ($ pem ),
235+ 4 => self ::tryToLoadX25519Key ($ details ), // OPENSSL_KEYTYPE_X25519
236+ 5 => self ::tryToLoadED25519Key ($ details ), // OPENSSL_KEYTYPE_ED25519
237+ 6 => self ::tryToLoadX448Key ($ details ), // OPENSSL_KEYTYPE_X448
238+ 7 => self ::tryToLoadED448Key ($ details ), // OPENSSL_KEYTYPE_ED448
239+ -1 => self ::tryToLoadOtherKeyTypes ($ details , $ pem ),
234240 default => throw new InvalidArgumentException ('Unsupported key type ' ),
235241 };
236242 }
237243
238244 /**
239245 * This method tries to load Ed448, X488, Ed25519 and X25519 keys.
240246 *
247+ * @param array{type: int, key: string} $details
248+ *
241249 * @return array<array-key, mixed>
242250 */
243- private static function tryToLoadECKey (string $ input ): array
251+ private static function tryToLoadECKey (array $ details , string $ input ): array
244252 {
245253 try {
246254 return ECKey::createFromPEM ($ input )->toArray ();
247255 } catch (Throwable ) {
248256 // no break
249257 }
250258 try {
251- return self ::tryToLoadOtherKeyTypes ($ input );
259+ return self ::tryToLoadOtherKeyTypes ($ details , $ input );
252260 } catch (Throwable ) {
253261 // no break
254262 }
255263 throw new InvalidArgumentException ('Unable to load the key. ' );
256264 }
257265
266+ /**
267+ * @param array{bits: int, type: int, key: string, x25519: array{pub_key?: string, priv_key?: string}} $input
268+ *
269+ * @return array<array-key, mixed>
270+ */
271+ private static function tryToLoadX25519Key (array $ input ): array
272+ {
273+ $ values = [
274+ 'kty ' => 'OKP ' ,
275+ 'crv ' => 'X25519 ' ,
276+ ];
277+ if (array_key_exists ('pub_key ' , $ input ['x25519 ' ])) {
278+ $ values ['x ' ] = Base64UrlSafe::encodeUnpadded ($ input ['x25519 ' ]['pub_key ' ]);
279+ } else {
280+ $ values ['x ' ] = self ::tryToLoadOtherKeyTypes ($ input , $ input ['key ' ])['x ' ];
281+ }
282+ if (array_key_exists ('priv_key ' , $ input ['x25519 ' ])) {
283+ $ values ['d ' ] = Base64UrlSafe::encodeUnpadded ($ input ['x25519 ' ]['priv_key ' ]);
284+ }
285+ 286+ return $ values ;
287+ }
288+ 289+ /**
290+ * @param array{bits: int, type: int, key: string, ed25519: array{pub_key?: string, priv_key?: string}} $input
291+ *
292+ * @return array<array-key, mixed>
293+ */
294+ private static function tryToLoadED25519Key (array $ input ): array
295+ {
296+ $ values = [
297+ 'kty ' => 'OKP ' ,
298+ 'crv ' => 'Ed25519 ' ,
299+ ];
300+ if (array_key_exists ('pub_key ' , $ input ['ed25519 ' ])) {
301+ $ values ['x ' ] = Base64UrlSafe::encodeUnpadded ($ input ['ed25519 ' ]['pub_key ' ]);
302+ } else {
303+ $ values ['x ' ] = self ::tryToLoadOtherKeyTypes ($ input , $ input ['key ' ])['x ' ];
304+ }
305+ if (array_key_exists ('priv_key ' , $ input ['ed25519 ' ])) {
306+ $ values ['d ' ] = Base64UrlSafe::encodeUnpadded ($ input ['ed25519 ' ]['priv_key ' ]);
307+ }
308+ 309+ return $ values ;
310+ }
311+ 312+ /**
313+ * @param array{bits: int, type: int, key: string, x448: array{pub_key?: string, priv_key?: string}} $input
314+ *
315+ * @return array<array-key, mixed>
316+ */
317+ private static function tryToLoadX448Key (array $ input ): array
318+ {
319+ $ values = [
320+ 'kty ' => 'OKP ' ,
321+ 'crv ' => 'X448 ' ,
322+ ];
323+ if (array_key_exists ('pub_key ' , $ input ['x448 ' ])) {
324+ $ values ['x ' ] = Base64UrlSafe::encodeUnpadded ($ input ['x448 ' ]['pub_key ' ]);
325+ } else {
326+ $ values ['x ' ] = self ::tryToLoadOtherKeyTypes ($ input , $ input ['key ' ])['x ' ];
327+ }
328+ if (array_key_exists ('priv_key ' , $ input ['x448 ' ])) {
329+ $ values ['d ' ] = Base64UrlSafe::encodeUnpadded ($ input ['x448 ' ]['priv_key ' ]);
330+ }
331+ 332+ return $ values ;
333+ }
334+ 335+ /**
336+ * @param array{bits: int, type: int, key: string, ed448: array{pub_key?: string, priv_key?: string}} $input
337+ *
338+ * @return array<array-key, mixed>
339+ */
340+ private static function tryToLoadED448Key (array $ input ): array
341+ {
342+ $ values = [
343+ 'kty ' => 'OKP ' ,
344+ 'crv ' => 'Ed448 ' ,
345+ ];
346+ if (array_key_exists ('pub_key ' , $ input ['ed448 ' ])) {
347+ $ values ['x ' ] = Base64UrlSafe::encodeUnpadded ($ input ['ed448 ' ]['pub_key ' ]);
348+ } else {
349+ $ values ['x ' ] = self ::tryToLoadOtherKeyTypes ($ input , $ input ['key ' ])['x ' ];
350+ }
351+ if (array_key_exists ('priv_key ' , $ input ['ed448 ' ])) {
352+ $ values ['d ' ] = Base64UrlSafe::encodeUnpadded ($ input ['ed448 ' ]['priv_key ' ]);
353+ }
354+ 355+ return $ values ;
356+ }
357+ 258358 /**
259359 * This method tries to load Ed448, X488, Ed25519 and X25519 keys.
360+ * Only needed on PHP8.3 and earlier.
361+ *
362+ * @param array{key: string} $details
260363 *
261364 * @return array<array-key, mixed>
262365 */
263- private static function tryToLoadOtherKeyTypes (string $ input ): array
366+ private static function tryToLoadOtherKeyTypes (array $ details , string $ input ): array
264367 {
265368 $ pem = PEM ::fromString ($ input );
266369 return match ($ pem ->type ()) {
267370 PEM ::TYPE_PUBLIC_KEY => self ::loadPublicKey ($ pem ),
268- PEM ::TYPE_PRIVATE_KEY => self ::loadPrivateKey ($ pem ),
371+ PEM ::TYPE_PRIVATE_KEY => self ::loadPrivateKey ($ details , $ pem ),
269372 default => throw new InvalidArgumentException ('Unsupported key type ' ),
270373 };
271374 }
272375
273376 /**
377+ * @param array{key: string} $details
378+ *
274379 * @return array<string, mixed>
275380 */
276- private static function loadPrivateKey (PEM $ pem ): array
381+ private static function loadPrivateKey (array $ details , PEM $ pem ): array
277382 {
278383 try {
279384 $ key = PrivateKey::fromPEM ($ pem );
@@ -296,12 +401,15 @@ private static function loadPrivateKey(PEM $pem): array
296401 case AlgorithmIdentifier::OID_X25519 :
297402 case AlgorithmIdentifier::OID_X448 :
298403 $ curve = self ::getCurve ($ key ->algorithmIdentifier ()->oid ());
299- $ values = [
404+ $ publicKey = PEM ::fromString ($ details ['key ' ]);
405+ /** @var UnspecifiedType $publicKeyBits */
406+ $ publicKeyBits = Sequence::fromDER ($ publicKey ->data ())->at (1 );
407+ return [
300408 'kty ' => 'OKP ' ,
301409 'crv ' => $ curve ,
410+ 'x ' => Base64UrlSafe::encodeUnpadded ($ publicKeyBits ->asBitString ()->string ()),
302411 'd ' => Base64UrlSafe::encodeUnpadded ($ key ->privateKeyData ()),
303412 ];
304- return self ::populatePoints ($ key , $ values );
305413 default :
306414 throw new InvalidArgumentException ('Unsupported key type ' );
307415 }
@@ -338,37 +446,6 @@ private static function convertDecimalToBas64Url(string $decimal): string
338446 return Base64UrlSafe::encodeUnpadded (BigInteger::fromBase ($ decimal , 10 )->toBytes ());
339447 }
340448
341- /**
342- * @param array<string, mixed> $values
343- * @return array<string, mixed>
344- */
345- private static function populatePoints (PrivateKey $ key , array $ values ): array
346- {
347- $ crv = $ values ['crv ' ] ?? null ;
348- assert (is_string ($ crv ), 'Unsupported key type. ' );
349- $ x = self ::getPublicKey ($ key , $ crv );
350- if ($ x !== null ) {
351- $ values ['x ' ] = Base64UrlSafe::encodeUnpadded ($ x );
352- }
353- 354- return $ values ;
355- }
356- 357- private static function getPublicKey (PrivateKey $ key , string $ crv ): ?string
358- {
359- switch ($ crv ) {
360- case 'Ed25519 ' :
361- return Ed25519::publickey_from_secretkey ($ key ->privateKeyData ());
362- case 'X25519 ' :
363- if (extension_loaded ('sodium ' )) {
364- return sodium_crypto_scalarmult_base ($ key ->privateKeyData ());
365- }
366- // no break
367- default :
368- return null ;
369- }
370- }
371- 372449 private static function checkType (string $ curve ): void
373450 {
374451 $ curves = ['Ed448ph ' , 'Ed25519ph ' , 'Ed448 ' , 'Ed25519 ' , 'X448 ' , 'X25519 ' ];
0 commit comments