refactor: flatten src/AKSRT → src (drop theme-identifier dir)
Move all domain folders from src/AKSRT/{Domain} to src/{Domain}; remap
Composer PSR-4 "AKSRT\\": "src/"; update audit script + README + ADR
paths; remove dead tools/build-psr4-map.php (scanned deleted inc/classes).
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* Encrypted identity provider credential storage.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Identity;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class SecretStore {
|
||||
|
||||
public const OPTION_KEY = 'aksrt_identity_secrets';
|
||||
private const CONTEXT = 'aksrt-identity-secrets-v1';
|
||||
|
||||
/** @return array<string,string> */
|
||||
public static function get(string $provider): array {
|
||||
$stored = get_option(self::OPTION_KEY, []);
|
||||
$record = is_array($stored) ? ($stored[sanitize_key($provider)] ?? null) : null;
|
||||
if (!is_array($record)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$plain = self::decrypt($record);
|
||||
if ($plain === '') {
|
||||
return [];
|
||||
}
|
||||
$data = json_decode($plain, true);
|
||||
if (!is_array($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$clean = [];
|
||||
foreach ($data as $key => $value) {
|
||||
if (is_string($key) && is_string($value)) {
|
||||
$clean[sanitize_key($key)] = $value;
|
||||
}
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
/** @param array<string,string|null> $changes */
|
||||
public static function update(string $provider, array $changes): bool {
|
||||
$provider = sanitize_key($provider);
|
||||
if (!in_array($provider, ['aliyun', 'tencent'], true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$current = self::get($provider);
|
||||
foreach ($changes as $key => $value) {
|
||||
$key = sanitize_key((string) $key);
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if ($value === null) {
|
||||
unset($current[$key]);
|
||||
} elseif (is_string($value) && $value !== '') {
|
||||
$current[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$stored = get_option(self::OPTION_KEY, []);
|
||||
$stored = is_array($stored) ? $stored : [];
|
||||
if ($current === []) {
|
||||
unset($stored[$provider]);
|
||||
} else {
|
||||
$record = self::encrypt((string) wp_json_encode($current));
|
||||
if (is_wp_error($record)) {
|
||||
return false;
|
||||
}
|
||||
$stored[$provider] = $record;
|
||||
}
|
||||
return update_option(self::OPTION_KEY, $stored, false) || get_option(self::OPTION_KEY, []) === $stored;
|
||||
}
|
||||
|
||||
/** @return array<string,array{configured:bool,fingerprint:string}> */
|
||||
public static function status(string $provider): array {
|
||||
$status = [];
|
||||
foreach (self::get($provider) as $key => $value) {
|
||||
$status[$key] = [
|
||||
'configured' => $value !== '',
|
||||
'fingerprint' => $value !== '' ? substr(hash('sha256', $value), -4) : '',
|
||||
];
|
||||
}
|
||||
return $status;
|
||||
}
|
||||
|
||||
/** @return array<string,string>|\WP_Error */
|
||||
private static function encrypt(string $plain) {
|
||||
$key = self::key();
|
||||
if (function_exists('sodium_crypto_aead_xchacha20poly1305_ietf_encrypt')) {
|
||||
$nonce = random_bytes(SODIUM_CRYPTO_AEAD_XCHACHA20POLY1305_IETF_NPUBBYTES);
|
||||
$cipher = sodium_crypto_aead_xchacha20poly1305_ietf_encrypt($plain, self::CONTEXT, $nonce, $key);
|
||||
return ['driver' => 'sodium', 'nonce' => base64_encode($nonce), 'cipher' => base64_encode($cipher)];
|
||||
}
|
||||
if (function_exists('openssl_encrypt')) {
|
||||
$nonce = random_bytes(12);
|
||||
$tag = '';
|
||||
$cipher = openssl_encrypt($plain, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag, self::CONTEXT);
|
||||
if (!is_string($cipher)) {
|
||||
return new \WP_Error('aksrt_identity_encrypt', __('身份认证凭证加密失败', 'aksrt'));
|
||||
}
|
||||
return ['driver' => 'openssl', 'nonce' => base64_encode($nonce), 'tag' => base64_encode($tag), 'cipher' => base64_encode($cipher)];
|
||||
}
|
||||
return new \WP_Error('aksrt_identity_crypto', __('服务器不支持安全凭证加密', 'aksrt'));
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $record */
|
||||
private static function decrypt(array $record): string {
|
||||
$nonce = base64_decode((string) ($record['nonce'] ?? ''), true);
|
||||
$cipher = base64_decode((string) ($record['cipher'] ?? ''), true);
|
||||
if (!is_string($nonce) || !is_string($cipher)) {
|
||||
return '';
|
||||
}
|
||||
$key = self::key();
|
||||
if (($record['driver'] ?? '') === 'sodium' && function_exists('sodium_crypto_aead_xchacha20poly1305_ietf_decrypt')) {
|
||||
$plain = sodium_crypto_aead_xchacha20poly1305_ietf_decrypt($cipher, self::CONTEXT, $nonce, $key);
|
||||
return is_string($plain) ? $plain : '';
|
||||
}
|
||||
if (($record['driver'] ?? '') === 'openssl' && function_exists('openssl_decrypt')) {
|
||||
$tag = base64_decode((string) ($record['tag'] ?? ''), true);
|
||||
if (!is_string($tag)) {
|
||||
return '';
|
||||
}
|
||||
$plain = openssl_decrypt($cipher, 'aes-256-gcm', $key, OPENSSL_RAW_DATA, $nonce, $tag, self::CONTEXT);
|
||||
return is_string($plain) ? $plain : '';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
private static function key(): string {
|
||||
return hash_hmac('sha256', self::CONTEXT, wp_salt('auth') . wp_salt('secure_auth'), true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user