Files
wordpress-theme-aksrt/src/Identity/SecretStore.php
T
kate522 8c5b1555c9 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).
2026-08-02 22:06:06 +08:00

136 lines
5.0 KiB
PHP

<?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);
}
}