27505f7501
Completes the MokoJoomCross → MokoSuiteCross rebrand across all language string keys, Joomla event names, documentation, and wiki pages. - 1,151 language key references renamed (COM_, PLG_, PKG_ prefixes) - Event names renamed (onMokoJoomCross* → onMokoSuiteCross*) - CLAUDE.md, CHANGELOG.md, wiki docs updated - Zero mokojoomcross references remaining in codebase Closes #128, closes #138
111 lines
3.2 KiB
PHP
111 lines
3.2 KiB
PHP
<?php
|
|
|
|
/**
|
|
* @package MokoSuiteCross
|
|
* @subpackage com_mokosuitecross
|
|
* @author Moko Consulting <hello@mokoconsulting.tech>
|
|
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
|
* @license GNU General Public License version 3 or later; see LICENSE
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
namespace Joomla\Component\MokoSuiteCross\Administrator\Helper;
|
|
|
|
defined('_JEXEC') or die;
|
|
|
|
use Joomla\CMS\Factory;
|
|
|
|
/**
|
|
* Encrypts and decrypts service credentials using libsodium.
|
|
*
|
|
* Uses Joomla's $secret from configuration.php as the key source.
|
|
* Falls back to plaintext JSON if sodium is unavailable or decryption
|
|
* fails (backward compat with existing unencrypted credentials).
|
|
*/
|
|
class CredentialHelper
|
|
{
|
|
private const PREFIX = 'enc:sodium:';
|
|
|
|
/**
|
|
* Encrypt a credentials array to a storable string.
|
|
*
|
|
* @param array $credentials Credentials to encrypt
|
|
*
|
|
* @return string Encrypted string prefixed with "enc:sodium:", or plain JSON as fallback
|
|
*/
|
|
public static function encrypt(array $credentials): string
|
|
{
|
|
$json = json_encode($credentials);
|
|
|
|
if (!function_exists('sodium_crypto_secretbox')) {
|
|
return $json;
|
|
}
|
|
|
|
try {
|
|
$key = self::deriveKey();
|
|
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
|
$cipher = sodium_crypto_secretbox($json, $nonce, $key);
|
|
|
|
return self::PREFIX . base64_encode($nonce . $cipher);
|
|
} catch (\Throwable $e) {
|
|
return $json;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decrypt a credentials string back to an array.
|
|
*
|
|
* Handles both encrypted (prefixed) and legacy plaintext JSON.
|
|
*
|
|
* @param string $stored Stored credential string
|
|
*
|
|
* @return array Decoded credentials
|
|
*/
|
|
public static function decrypt(string $stored): array
|
|
{
|
|
if (empty($stored)) {
|
|
return [];
|
|
}
|
|
|
|
// Legacy plaintext JSON — no prefix
|
|
if (!str_starts_with($stored, self::PREFIX)) {
|
|
return json_decode($stored, true) ?: [];
|
|
}
|
|
|
|
if (!function_exists('sodium_crypto_secretbox_open')) {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$key = self::deriveKey();
|
|
$payload = base64_decode(substr($stored, strlen(self::PREFIX)));
|
|
|
|
if ($payload === false || strlen($payload) < SODIUM_CRYPTO_SECRETBOX_NONCEBYTES) {
|
|
return [];
|
|
}
|
|
|
|
$nonce = substr($payload, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
|
$cipher = substr($payload, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
|
|
$plain = sodium_crypto_secretbox_open($cipher, $nonce, $key);
|
|
|
|
if ($plain === false) {
|
|
return [];
|
|
}
|
|
|
|
return json_decode($plain, true) ?: [];
|
|
} catch (\Throwable $e) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derive a 32-byte encryption key from Joomla's secret.
|
|
*/
|
|
private static function deriveKey(): string
|
|
{
|
|
$secret = Factory::getApplication()->get('secret', '');
|
|
|
|
return sodium_crypto_generichash($secret, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
|
|
}
|
|
}
|