178 lines
5.4 KiB
PHP
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;
|
|
}
|
|
}
|