305 lines
9.0 KiB
PHP
305 lines
9.0 KiB
PHP
![]() |
<?php
|
||
|
|
||
|
declare(strict_types=1);
|
||
|
|
||
|
/*
|
||
|
* The MIT License (MIT)
|
||
|
*
|
||
|
* Copyright (c) 2014-2018 Spomky-Labs
|
||
|
*
|
||
|
* This software may be modified and distributed under the terms
|
||
|
* of the MIT license. See the LICENSE file for details.
|
||
|
*/
|
||
|
|
||
|
namespace Jose\Component\KeyManagement;
|
||
|
|
||
|
use Base64Url\Base64Url;
|
||
|
use InvalidArgumentException;
|
||
|
use Jose\Component\Core\JWK;
|
||
|
use Jose\Component\Core\JWKSet;
|
||
|
use Jose\Component\Core\Util\Ecc\Curve;
|
||
|
use Jose\Component\Core\Util\ECKey;
|
||
|
use Jose\Component\KeyManagement\KeyConverter\KeyConverter;
|
||
|
use Jose\Component\KeyManagement\KeyConverter\RSAKey;
|
||
|
use function openssl_pkey_get_details;
|
||
|
use function openssl_pkey_new;
|
||
|
use RuntimeException;
|
||
|
|
||
|
class JWKFactory
|
||
|
{
|
||
|
/**
|
||
|
* Creates a RSA key with the given key size and additional values.
|
||
|
*
|
||
|
* @param int $size The key size in bits
|
||
|
* @param array $values values to configure the key
|
||
|
*/
|
||
|
public static function createRSAKey(int $size, array $values = []): JWK
|
||
|
{
|
||
|
if (0 !== $size % 8) {
|
||
|
throw new InvalidArgumentException('Invalid key size.');
|
||
|
}
|
||
|
|
||
|
if (512 > $size) {
|
||
|
throw new InvalidArgumentException('Key length is too short. It needs to be at least 512 bits.');
|
||
|
}
|
||
|
|
||
|
$key = openssl_pkey_new([
|
||
|
'private_key_bits' => $size,
|
||
|
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||
|
]);
|
||
|
$details = openssl_pkey_get_details($key);
|
||
|
\openssl_free_key($key);
|
||
|
$rsa = RSAKey::createFromKeyDetails($details['rsa']);
|
||
|
$values = \array_merge(
|
||
|
$values,
|
||
|
$rsa->toArray()
|
||
|
);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a EC key with the given curve and additional values.
|
||
|
*
|
||
|
* @param string $curve The curve
|
||
|
* @param array $values values to configure the key
|
||
|
*/
|
||
|
public static function createECKey(string $curve, array $values = []): JWK
|
||
|
{
|
||
|
return ECKey::createECKey($curve, $values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a octet key with the given key size and additional values.
|
||
|
*
|
||
|
* @param int $size The key size in bits
|
||
|
* @param array $values values to configure the key
|
||
|
*/
|
||
|
public static function createOctKey(int $size, array $values = []): JWK
|
||
|
{
|
||
|
if (0 !== $size % 8) {
|
||
|
throw new InvalidArgumentException('Invalid key size.');
|
||
|
}
|
||
|
$values = \array_merge(
|
||
|
$values,
|
||
|
[
|
||
|
'kty' => 'oct',
|
||
|
'k' => Base64Url::encode(\random_bytes($size / 8)),
|
||
|
]
|
||
|
);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a OKP key with the given curve and additional values.
|
||
|
*
|
||
|
* @param string $curve The curve
|
||
|
* @param array $values values to configure the key
|
||
|
*/
|
||
|
public static function createOKPKey(string $curve, array $values = []): JWK
|
||
|
{
|
||
|
switch ($curve) {
|
||
|
case 'X25519':
|
||
|
$keyPair = \sodium_crypto_box_keypair();
|
||
|
$secret = \sodium_crypto_box_secretkey($keyPair);
|
||
|
$x = \sodium_crypto_box_publickey($keyPair);
|
||
|
|
||
|
break;
|
||
|
case 'Ed25519':
|
||
|
$keyPair = \sodium_crypto_sign_keypair();
|
||
|
$secret = \sodium_crypto_sign_secretkey($keyPair);
|
||
|
$x = \sodium_crypto_sign_publickey($keyPair);
|
||
|
|
||
|
break;
|
||
|
default:
|
||
|
throw new InvalidArgumentException(\sprintf('Unsupported "%s" curve', $curve));
|
||
|
}
|
||
|
$secretLength = mb_strlen($secret, '8bit');
|
||
|
$d = mb_substr($secret, 0, -$secretLength / 2, '8bit');
|
||
|
|
||
|
$values = \array_merge(
|
||
|
$values,
|
||
|
[
|
||
|
'kty' => 'OKP',
|
||
|
'crv' => $curve,
|
||
|
'd' => Base64Url::encode($d),
|
||
|
'x' => Base64Url::encode($x),
|
||
|
]
|
||
|
);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a none key with the given additional values.
|
||
|
* Please note that this key type is not pat of any specification.
|
||
|
* It is used to prevent the use of the "none" algorithm with other key types.
|
||
|
*
|
||
|
* @param array $values values to configure the key
|
||
|
*/
|
||
|
public static function createNoneKey(array $values = []): JWK
|
||
|
{
|
||
|
$values = \array_merge(
|
||
|
$values,
|
||
|
[
|
||
|
'kty' => 'none',
|
||
|
'alg' => 'none',
|
||
|
'use' => 'sig',
|
||
|
]
|
||
|
);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a key from a Json string.
|
||
|
*
|
||
|
* @return JWK|JWKSet
|
||
|
*/
|
||
|
public static function createFromJsonObject(string $value)
|
||
|
{
|
||
|
$json = \json_decode($value, true);
|
||
|
if (!\is_array($json)) {
|
||
|
throw new InvalidArgumentException('Invalid key or key set.');
|
||
|
}
|
||
|
|
||
|
return self::createFromValues($json);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a key or key set from the given input.
|
||
|
*
|
||
|
* @return JWK|JWKSet
|
||
|
*/
|
||
|
public static function createFromValues(array $values)
|
||
|
{
|
||
|
if (\array_key_exists('keys', $values) && \is_array($values['keys'])) {
|
||
|
return JWKSet::createFromKeyData($values);
|
||
|
}
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method create a JWK object using a shared secret.
|
||
|
*/
|
||
|
public static function createFromSecret(string $secret, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = \array_merge(
|
||
|
$additional_values,
|
||
|
[
|
||
|
'kty' => 'oct',
|
||
|
'k' => Base64Url::encode($secret),
|
||
|
]
|
||
|
);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to load a X.509 certificate and convert it into a public key.
|
||
|
*/
|
||
|
public static function createFromCertificateFile(string $file, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadKeyFromCertificateFile($file);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract a keyfrom a key set identified by the given index .
|
||
|
*
|
||
|
* @param int|string $index
|
||
|
*/
|
||
|
public static function createFromKeySet(JWKSet $jwkset, $index): JWK
|
||
|
{
|
||
|
return $jwkset->get($index);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to load a PKCS#12 file and convert it into a public key.
|
||
|
*
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public static function createFromPKCS12CertificateFile(string $file, ?string $secret = '', array $additional_values = []): JWK
|
||
|
{
|
||
|
$res = \openssl_pkcs12_read(\file_get_contents($file), $certs, $secret);
|
||
|
if (false === $res || !\is_array($certs) || !\array_key_exists('pkey', $certs)) {
|
||
|
throw new RuntimeException('Unable to load the certificates.');
|
||
|
}
|
||
|
|
||
|
return self::createFromKey($certs['pkey'], null, $additional_values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to convert a X.509 certificate into a public key.
|
||
|
*/
|
||
|
public static function createFromCertificate(string $certificate, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadKeyFromCertificate($certificate);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to convert a X.509 certificate resource into a public key.
|
||
|
*
|
||
|
* @param resource $res
|
||
|
*
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public static function createFromX509Resource($res, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadKeyFromX509Resource($res);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to load and convert a key file into a JWK object.
|
||
|
* If the key is encrypted, the password must be set.
|
||
|
*
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public static function createFromKeyFile(string $file, ?string $password = null, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadFromKeyFile($file, $password);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to load and convert a key into a JWK object.
|
||
|
* If the key is encrypted, the password must be set.
|
||
|
*
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public static function createFromKey(string $key, ?string $password = null, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadFromKey($key, $password);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This method will try to load and convert a X.509 certificate chain into a public key.
|
||
|
*
|
||
|
* Be careful! The certificate chain is loaded, but it is NOT VERIFIED by any mean!
|
||
|
* It is mandatory to verify the root CA or intermediate CA are trusted.
|
||
|
* If not done, it may lead to potential security issues.
|
||
|
*/
|
||
|
public static function createFromX5C(array $x5c, array $additional_values = []): JWK
|
||
|
{
|
||
|
$values = KeyConverter::loadFromX5C($x5c);
|
||
|
$values = \array_merge($values, $additional_values);
|
||
|
|
||
|
return new JWK($values);
|
||
|
}
|
||
|
}
|