misuzu/src/OpenID/OpenIDContext.php
2025-02-25 02:30:24 +00:00

178 lines
5.4 KiB
PHP

<?php
namespace Misuzu\OpenID;
use RuntimeException;
use Index\Config\Config;
use Misuzu\{Misuzu,SiteInfo};
use Misuzu\Apps\AppInfo;
use Misuzu\OAuth2\{OAuth2AccessInfo,OAuth2AuthorisationInfo};
use Misuzu\Users\UserInfo;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\Common\{AsymmetricKey,PrivateKey,PublicKey};
class OpenIDContext {
private const string KEY_FORMAT = '%s/keys/%s-%s.pem';
public function __construct(
private SiteInfo $siteInfo,
private Config $config,
) {}
/** @var string[] */
public array $keyIds {
get => $this->config->getArray('keys');
}
public string $websiteProfileField {
get => $this->config->getString('website_profile_field');
}
public static function getDataForIdToken(
SiteInfo $siteInfo,
AppInfo $appInfo,
OAuth2AccessInfo|OAuth2AuthorisationInfo $accessOrAuthzInfo,
?string $nonce = null,
?int $issuedAt = null
): array {
$token = [
'iss' => $siteInfo->url,
'sub' => $accessOrAuthzInfo->userId,
'aud' => $appInfo->clientId,
'exp' => $accessOrAuthzInfo->expiresTime,
'iat' => $issuedAt ?? time(),
'auth_time' => $accessOrAuthzInfo->createdTime,
];
if($nonce !== null) // keep track of this somehow, must be taken from /oauth2/authorize args
$token['nonce'] = $nonce;
return $token;
}
public function createIdToken(
AppInfo $appInfo,
OAuth2AccessInfo|OAuth2AuthorisationInfo $accessOrAuthzInfo,
?string $nonce = null,
?int $issuedAt = null,
?string $keyId = null
): string {
return JWT::encode(
self::getDataForIdToken(
$this->siteInfo,
$appInfo,
$accessOrAuthzInfo,
$nonce,
$issuedAt
),
$this->getPrivateJWTKeySet()->getKey($keyId),
$keyId
);
}
public function getPublicKey(string $keyId): AsymmetricKey&PublicKey {
$path = realpath(sprintf(self::KEY_FORMAT, Misuzu::PATH_CONFIG, $keyId, 'pub'));
if($path === false)
throw new RuntimeException(sprintf('public key "%s" could not be found', $keyId));
$body = file_get_contents($path);
if($body === false)
throw new RuntimeException(sprintf('public key "%s" could not be read', $keyId));
return PublicKeyLoader::loadPublicKey($body);
}
/** @return array<string, AsymmetricKey&PublicKey> */
public function getPublicKeys(): array {
$keys = [];
foreach($this->keyIds as $keyId)
$keys[$keyId] = $this->getPublicKey($keyId);
return $keys;
}
public function getPublicJWTKeySet(): JWTKeySet {
$keys = [];
foreach($this->keyIds as $keyId) {
$key = $this->getPublicKey($keyId);
$keys[$keyId] = new SecLibJWTKey(SecLibJWTKey::inferAlgorithm($key), $key);
}
return new ArrayJWTKeySet($keys);
}
/** @return array<string, mixed[]> */
public function getPublicJWTsForJson(): array {
$keys = [];
foreach($this->keyIds as $keyId) {
$publicKey = $this->getPublicKey($keyId);
$options = [
'kid' => $keyId,
'use' => 'sig',
'alg' => SecLibJWTKey::inferAlgorithm($publicKey),
];
$keys = array_merge_recursive(
$keys,
json_decode($publicKey->toString('JWK', $options), true)
);
}
return $keys;
}
public function getFirstPrivateKey(): AsymmetricKey&PrivateKey {
return $this->getPrivateKey($this->keyIds[0]);
}
public function getPrivateKey(string $keyId): AsymmetricKey&PrivateKey {
$path = realpath(sprintf(self::KEY_FORMAT, Misuzu::PATH_CONFIG, $keyId, 'priv'));
if($path === false)
throw new RuntimeException(sprintf('private key "%s" could not be found', $keyId));
$body = file_get_contents($path);
if($body === false)
throw new RuntimeException(sprintf('private key "%s" could not be read', $keyId));
return PublicKeyLoader::loadPrivateKey($body);
}
/** @return array<string, AsymmetricKey&PrivateKey> */
public function getPrivateKeys(): array {
$keys = [];
foreach($this->keyIds as $keyId)
$keys[$keyId] = $this->getPrivateKey($keyId);
return $keys;
}
public function getPrivateJWTKeySet(): JWTKeySet {
$keys = [];
foreach($this->keyIds as $keyId) {
$key = $this->getPrivateKey($keyId);
$keys[$keyId] = new SecLibJWTKey(SecLibJWTKey::inferAlgorithm($key), $key);
}
return new ArrayJWTKeySet($keys);
}
/** @return array<string, mixed[]> */
public function getPrivateJWTsForJson(): array {
$keys = [];
foreach($this->keyIds as $keyId) {
$privateKey = $this->getPrivateKey($keyId);
$options = [
'kid' => $keyId,
'use' => 'sig',
'alg' => SecLibJWTKey::inferAlgorithm($privateKey),
];
$keys = array_merge_recursive(
$keys,
json_decode($privateKey->toString('JWK', $options), true)
);
}
return $keys;
}
}