10
10
use OpenSSLCertificate ;
11
11
use ParagonIE \Sodium \Core \Ed25519 ;
12
12
use RuntimeException ;
13
+ use SpomkyLabs \Pki \ASN1 \Type \Constructed \Sequence ;
14
+ use SpomkyLabs \Pki \ASN1 \Type \UnspecifiedType ;
13
15
use SpomkyLabs \Pki \CryptoEncoding \PEM ;
14
16
use SpomkyLabs \Pki \CryptoTypes \AlgorithmIdentifier \AlgorithmIdentifier ;
15
17
use SpomkyLabs \Pki \CryptoTypes \Asymmetric \PrivateKey ;
@@ -228,52 +230,155 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a
228
230
}
229
231
230
232
return match ($ details ['type ' ]) {
231
- OPENSSL_KEYTYPE_EC => self ::tryToLoadECKey ($ pem ),
233
+ OPENSSL_KEYTYPE_EC => self ::tryToLoadECKey ($ details , $ pem ),
232
234
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 ),
234
240
default => throw new InvalidArgumentException ('Unsupported key type ' ),
235
241
};
236
242
}
237
243
238
244
/**
239
245
* This method tries to load Ed448, X488, Ed25519 and X25519 keys.
240
246
*
247
+ * @param array{type: int, key: string} $details
248
+ *
241
249
* @return array<array-key, mixed>
242
250
*/
243
- private static function tryToLoadECKey (string $ input ): array
251
+ private static function tryToLoadECKey (array $ details , string $ input ): array
244
252
{
245
253
try {
246
254
return ECKey::createFromPEM ($ input )->toArray ();
247
255
} catch (Throwable ) {
248
256
// no break
249
257
}
250
258
try {
251
- return self ::tryToLoadOtherKeyTypes ($ input );
259
+ return self ::tryToLoadOtherKeyTypes ($ details , $ input );
252
260
} catch (Throwable ) {
253
261
// no break
254
262
}
255
263
throw new InvalidArgumentException ('Unable to load the key. ' );
256
264
}
257
265
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
+
258
358
/**
259
359
* 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
260
363
*
261
364
* @return array<array-key, mixed>
262
365
*/
263
- private static function tryToLoadOtherKeyTypes (string $ input ): array
366
+ private static function tryToLoadOtherKeyTypes (array $ details , string $ input ): array
264
367
{
265
368
$ pem = PEM ::fromString ($ input );
266
369
return match ($ pem ->type ()) {
267
370
PEM ::TYPE_PUBLIC_KEY => self ::loadPublicKey ($ pem ),
268
- PEM ::TYPE_PRIVATE_KEY => self ::loadPrivateKey ($ pem ),
371
+ PEM ::TYPE_PRIVATE_KEY => self ::loadPrivateKey ($ details , $ pem ),
269
372
default => throw new InvalidArgumentException ('Unsupported key type ' ),
270
373
};
271
374
}
272
375
273
376
/**
377
+ * @param array{key: string} $details
378
+ *
274
379
* @return array<string, mixed>
275
380
*/
276
- private static function loadPrivateKey (PEM $ pem ): array
381
+ private static function loadPrivateKey (array $ details , PEM $ pem ): array
277
382
{
278
383
try {
279
384
$ key = PrivateKey::fromPEM ($ pem );
@@ -296,12 +401,15 @@ private static function loadPrivateKey(PEM $pem): array
296
401
case AlgorithmIdentifier::OID_X25519 :
297
402
case AlgorithmIdentifier::OID_X448 :
298
403
$ 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 [
300
408
'kty ' => 'OKP ' ,
301
409
'crv ' => $ curve ,
410
+ 'x ' => Base64UrlSafe::encodeUnpadded ($ publicKeyBits ->asBitString ()->string ()),
302
411
'd ' => Base64UrlSafe::encodeUnpadded ($ key ->privateKeyData ()),
303
412
];
304
- return self ::populatePoints ($ key , $ values );
305
413
default :
306
414
throw new InvalidArgumentException ('Unsupported key type ' );
307
415
}
@@ -338,37 +446,6 @@ private static function convertDecimalToBas64Url(string $decimal): string
338
446
return Base64UrlSafe::encodeUnpadded (BigInteger::fromBase ($ decimal , 10 )->toBytes ());
339
447
}
340
448
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
-
372
449
private static function checkType (string $ curve ): void
373
450
{
374
451
$ curves = ['Ed448ph ' , 'Ed25519ph ' , 'Ed448 ' , 'Ed25519 ' , 'X448 ' , 'X25519 ' ];
0 commit comments