* @copyright Copyright (c) Alex Bilbie * @license http://mit-license.org/ * * @link https://github.com/thephpleague/oauth2-server */ declare(strict_types=1); namespace League\OAuth2\Server; use LogicException; use OpenSSLAsymmetricKey; use SensitiveParameter; use function decoct; use function file_get_contents; use function fileperms; use function in_array; use function is_file; use function is_readable; use function openssl_pkey_get_details; use function openssl_pkey_get_private; use function openssl_pkey_get_public; use function sprintf; use function trigger_error; class CryptKey implements CryptKeyInterface { private const FILE_PREFIX = 'file://'; /** * @var string Key contents */ protected string $keyContents; protected string $keyPath; public function __construct( string $keyPath, #[SensitiveParameter] protected ?string $passPhrase = null, bool $keyPermissionsCheck = true ) { if (str_starts_with($keyPath, self::FILE_PREFIX) === false && $this->isValidKey($keyPath, $this->passPhrase ?? '')) { $this->keyContents = $keyPath; $this->keyPath = ''; // There's no file, so no need for permission check. $keyPermissionsCheck = false; } elseif (is_file($keyPath)) { if (str_starts_with($keyPath, self::FILE_PREFIX) === false) { $keyPath = self::FILE_PREFIX . $keyPath; } if (!is_readable($keyPath)) { throw new LogicException(sprintf('Key path "%s" does not exist or is not readable', $keyPath)); } $keyContents = file_get_contents($keyPath); if ($keyContents === false) { throw new LogicException('Unable to read key from file ' . $keyPath); } $this->keyContents = $keyContents; $this->keyPath = $keyPath; if (!$this->isValidKey($this->keyContents, $this->passPhrase ?? '')) { throw new LogicException('Unable to read key from file ' . $keyPath); } } else { throw new LogicException('Invalid key supplied'); } if ($keyPermissionsCheck === true && PHP_OS_FAMILY !== 'Windows') { // Verify the permissions of the key $keyPathPerms = decoct(fileperms($this->keyPath) & 0777); if (in_array($keyPathPerms, ['400', '440', '600', '640', '660'], true) === false) { trigger_error( sprintf( 'Key file "%s" permissions are not correct, recommend changing to 600 or 660 instead of %s', $this->keyPath, $keyPathPerms ), E_USER_NOTICE ); } } } /** * {@inheritdoc} */ public function getKeyContents(): string { return $this->keyContents; } /** * Validate key contents. */ private function isValidKey( #[SensitiveParameter] string $contents, #[SensitiveParameter] string $passPhrase ): bool { $privateKey = openssl_pkey_get_private($contents, $passPhrase); $key = $privateKey instanceof OpenSSLAsymmetricKey ? $privateKey : openssl_pkey_get_public($contents); if ($key === false) { return false; } $details = openssl_pkey_get_details($key); return $details !== false && in_array( $details['type'] ?? -1, [OPENSSL_KEYTYPE_RSA, OPENSSL_KEYTYPE_EC], true ); } /** * {@inheritdoc} */ public function getKeyPath(): string { return $this->keyPath; } /** * {@inheritdoc} */ public function getPassPhrase(): ?string { return $this->passPhrase; } }