Skip to content
5 changes: 4 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,17 @@ jobs:
cat data/config/config.php
docker compose exec -T ec-cube composer dumpautoload
- name: Run to PHPUnit
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit --exclude-group classloader,mysql_prepare
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit --exclude-group classloader,mysql_prepare,auth_type_plain
- name: Run to PHPUnit classloader
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit --group classloader
- name: Run to PHPUnit mysql_prepare
# XXX 連続してテストを実行すると、何故か MySQL の prepare statement に失敗するため個別に実行する
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit --group mysql_prepare
- name: Run to PHPUnit SessionFactory
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit tests/class/SC_SessionFactoryTest.php
- name: Run to PHPUnit AUTH_TYPE=PLAIN
# AUTH_TYPE は定数のためまとめて実行できない. PLAIN用のbootstrapで個別に実行する
run: docker compose exec -T ec-cube php data/vendor/bin/phpunit --no-configuration --bootstrap tests/require_auth_type_plain.php --group auth_type_plain tests/class/util/SC_Utils/
- name: Run to Email-template compatibility tests
# 2.17.2-p2 のメールテンプレートで正常にテストが通るかチェックする
run: |
Expand Down
14 changes: 14 additions & 0 deletions data/class/SC_Customer.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ public function getCustomerDataFromEmailPass($pass, $email, $mobile = false)

// パスワードが合っていれば会員情報をcustomer_dataにセットしてtrueを返す
if (SC_Utils_Ex::sfIsMatchHashPassword($pass, $data['password'], $data['salt'])) {
// パスワードハッシュの自動マイグレーション
if (SC_Utils_Ex::sfNeedsReHash($data['password'], $data['salt'])) {
$arrNewHash = SC_Utils_Ex::sfReHashPassword($pass);
$objQuery->update('dtb_customer', $arrNewHash, 'customer_id = ?', [$data['customer_id']]);
$data['password'] = $arrNewHash['password'];
$data['salt'] = $arrNewHash['salt'];
}
$this->customer_data = $data;
$this->startSession();

Expand Down Expand Up @@ -132,6 +139,13 @@ public function getCustomerDataFromMobilePhoneIdPass($pass)

// パスワードが合っている場合は、会員情報をcustomer_dataに格納してtrueを返す。
if (SC_Utils_Ex::sfIsMatchHashPassword($pass, $data['password'], $data['salt'])) {
// パスワードハッシュの自動マイグレーション
if (SC_Utils_Ex::sfNeedsReHash($data['password'], $data['salt'])) {
$arrNewHash = SC_Utils_Ex::sfReHashPassword($pass);
$objQuery->update('dtb_customer', $arrNewHash, 'customer_id = ?', [$data['customer_id']]);
$data['password'] = $arrNewHash['password'];
$data['salt'] = $arrNewHash['salt'];
}
$this->customer_data = $data;
$this->startSession();

Expand Down
2 changes: 1 addition & 1 deletion data/class/SC_Initial.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function requireInitialConfig()
define('ADMIN_FORCE_SSL', '');
define('ADMIN_ALLOW_HOSTS', '');
define('AUTH_MAGIC', '');
define('PASSWORD_HASH_ALGOS', 'sha256');
define('PASSWORD_HASH_ALGOS', PASSWORD_DEFAULT);
define('MAIL_BACKEND', 'mail');
define('SMTP_HOST', '');
define('SMTP_PORT', '');
Expand Down
8 changes: 7 additions & 1 deletion data/class/pages/admin/LC_Page_Admin_Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ public function lfIsLoginMember($login_id, $pass)
{
$objQuery = SC_Query_Ex::getSingletonInstance();
// パスワード、saltの取得
$cols = 'password, salt';
$cols = 'member_id, password, salt';
$table = 'dtb_member';
$where = 'login_id = ? AND del_flg <> 1 AND work = 1';
$arrData = $objQuery->getRow($cols, $table, $where, [$login_id]);
Expand All @@ -148,6 +148,12 @@ public function lfIsLoginMember($login_id, $pass)
}
// ユーザー入力パスワードの判定
if (SC_Utils_Ex::sfIsMatchHashPassword($pass, $arrData['password'], $arrData['salt'])) {
// パスワードハッシュの自動マイグレーション
if (SC_Utils_Ex::sfNeedsReHash($arrData['password'], $arrData['salt'])) {
$arrNewHash = SC_Utils_Ex::sfReHashPassword($pass);
$objQuery->update('dtb_member', $arrNewHash, 'member_id = ?', [$arrData['member_id']]);
}

return true;
}

Expand Down
123 changes: 99 additions & 24 deletions data/class/util/SC_Utils.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
*/
class SC_Utils
{
/** password_hash() 使用時のダミー salt. ソルトはハッシュ値に内包されるため不要だが, dtb_member.salt の NOT NULL 制約を満たすために使用する. */
public const PASSWORD_HASH_SALT_DUMMY = 'salt_is_included_in_hash';

// インストール初期処理
public static function sfInitInstall()
{
Expand Down Expand Up @@ -1771,18 +1774,21 @@ public static function isInternalUrl($url)
*
* @return string ハッシュ暗号化された文字列
*/
public static function sfGetHashString($str, $salt)
public static function sfGetHashString($str, $salt = '')
{
if (AUTH_TYPE == 'PLAIN') {
return $str;
}
// PHP password_hash() 対応アルゴリズムの場合
if (SC_Utils_Ex::sfIsPasswordHashAlgos()) {
return password_hash($str, PASSWORD_HASH_ALGOS);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
// 既存の HMAC-SHA256
if ($salt == '') {
$salt = AUTH_MAGIC;
}
if (AUTH_TYPE == 'PLAIN') {
$res = $str;
} else {
$res = hash_hmac(PASSWORD_HASH_ALGOS, $str.':'.AUTH_MAGIC, $salt);
}

return $res;
return hash_hmac('sha256', $str.':'.AUTH_MAGIC, $salt);
}
Comment thread
nanasess marked this conversation as resolved.

/**
Expand All @@ -1796,26 +1802,95 @@ public static function sfGetHashString($str, $salt)
*/
public static function sfIsMatchHashPassword($pass, $hashpass, $salt)
{
$res = false;
if ($hashpass != '') {
if (AUTH_TYPE == 'PLAIN') {
if ($pass === $hashpass) {
$res = true;
}
} else {
if (empty($salt)) {
// 旧バージョン(2.11未満)からの移行を考慮
$hash = sha1($pass.':'.AUTH_MAGIC);
} else {
$hash = SC_Utils_Ex::sfGetHashString($pass, $salt);
}
if ($hash === $hashpass) {
$res = true;
}
if ($hashpass == '') {
return false;
}
if (AUTH_TYPE == 'PLAIN') {
return $pass === $hashpass;
}
// password_hash() 形式のハッシュを検出 ($2y$..., $argon2id$... 等)
$info = password_get_info($hashpass);
if ($info['algo'] !== null && $info['algo'] !== 0) {
return password_verify($pass, $hashpass);
}
// 既存ロジック: HMAC-SHA256 / SHA1
if (empty($salt)) {
// 旧バージョン(2.11未満)からの移行を考慮
$hash = sha1($pass.':'.AUTH_MAGIC);
} else {
// レガシーハッシュの検証は常にHMAC-SHA256を使用
$hash = hash_hmac('sha256', $pass.':'.AUTH_MAGIC, $salt);
}

return $hash === $hashpass;
}

/**
* PASSWORD_HASH_ALGOS が PHP の password_hash() 対応アルゴリズムかどうか判定する
*
* @return bool password_hash() 対応アルゴリズムの場合 true
*/
public static function sfIsPasswordHashAlgos()
{
return in_array(PASSWORD_HASH_ALGOS, password_algos(), true);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

/**
* パスワードの再ハッシュが必要かどうか判定する
*
* @param string $hashpass パスワードハッシュ文字列
* @param string $salt salt
*
* @return bool 再ハッシュが必要な場合 true
*/
public static function sfNeedsReHash($hashpass, $salt)
{
if (AUTH_TYPE == 'PLAIN') {
return false;
}
// password_hash() 形式のハッシュの場合
$info = password_get_info($hashpass);
if ($info['algo'] !== null && $info['algo'] !== 0) {
if (SC_Utils_Ex::sfIsPasswordHashAlgos()) {
return password_needs_rehash($hashpass, PASSWORD_HASH_ALGOS);
}

return false;
}
// 旧形式(SHA1/HMAC-SHA256)のハッシュ
// 現在の設定が password_hash() 対応アルゴリズムなら再ハッシュが必要
if (SC_Utils_Ex::sfIsPasswordHashAlgos()) {
return true;
}
// 現在の設定がHMAC-SHA256で、saltが空(SHA1) → 再ハッシュ必要
if (empty($salt)) {
return true;
}

return false;
Comment thread
nanasess marked this conversation as resolved.
}

/**
* パスワードを現在のアルゴリズムで再ハッシュする
*
* @param string $pass 平文パスワード
*
* @return array ['password' => ハッシュ値, 'salt' => salt]
*/
public static function sfReHashPassword($pass)
{
if (SC_Utils_Ex::sfIsPasswordHashAlgos()) {
return [
'password' => password_hash($pass, PASSWORD_HASH_ALGOS),
'salt' => self::PASSWORD_HASH_SALT_DUMMY,
];
}
$salt = SC_Utils_Ex::sfGetRandomString(10);

return $res;
return [
'password' => SC_Utils_Ex::sfGetHashString($pass, $salt),
'salt' => $salt,
];
}

/**
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ services:
ADMIN_FORCE_SSL: 'false'
ADMIN_ALLOW_HOSTS: 'a:0:{}'
AUTH_MAGIC: ~
PASSWORD_HASH_ALGOS: sha256
PASSWORD_HASH_ALGOS: 2y

mysql:
image: mysql:8.4
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.pgsql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ services:
ADMIN_FORCE_SSL: 'false'
ADMIN_ALLOW_HOSTS: 'a:0:{}'
AUTH_MAGIC: ~
PASSWORD_HASH_ALGOS: sha256
PASSWORD_HASH_ALGOS: 2y

postgres:
image: postgres:18
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.sqlite3.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ services:
ADMIN_FORCE_SSL: 'false'
ADMIN_ALLOW_HOSTS: 'a:0:{}'
AUTH_MAGIC: ~
PASSWORD_HASH_ALGOS: sha256
PASSWORD_HASH_ALGOS: 2y
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ services:
ADMIN_FORCE_SSL: 'false'
ADMIN_ALLOW_HOSTS: 'a:0:{}'
AUTH_MAGIC: ~
PASSWORD_HASH_ALGOS: sha256
PASSWORD_HASH_ALGOS: 2y
MAIL_BACKEND: smtp
SMTP_HOST: mailcatcher
SMTP_PORT: 1025
Expand Down
2 changes: 1 addition & 1 deletion eccube_install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ defined('ADMIN_DIR') or define('ADMIN_DIR', '${ADMIN_DIR}');
defined('ADMIN_FORCE_SSL') or define('ADMIN_FORCE_SSL', FALSE);
defined('ADMIN_ALLOW_HOSTS') or define('ADMIN_ALLOW_HOSTS', 'a:0:{}');
defined('AUTH_MAGIC') or define('AUTH_MAGIC', '${AUTH_MAGIC}');
defined('PASSWORD_HASH_ALGOS') or define('PASSWORD_HASH_ALGOS', 'sha256');
defined('PASSWORD_HASH_ALGOS') or define('PASSWORD_HASH_ALGOS', PASSWORD_DEFAULT);
defined('MAIL_BACKEND') or define('MAIL_BACKEND', '${MAIL_BACKEND}');
defined('SMTP_HOST') or define('SMTP_HOST', '${SMTP_HOST}');
defined('SMTP_PORT') or define('SMTP_PORT', '${SMTP_PORT}');
Expand Down
11 changes: 1 addition & 10 deletions html/install/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -1014,16 +1014,7 @@ function lfMakeConfigFile()
}
}
//パスワード暗号化方式決定
$arrAlgos = hash_algos();
if (array_search('sha256', $arrAlgos) !== FALSE) {
$algos = 'sha256';
} elseif (array_search('sha1', $arrAlgos) !== FALSE) {
$algos = 'sha1';
} elseif (array_search('md5', $arrAlgos) !== FALSE) {
$algos = 'md5';
} else {
$algos = '';
}
$algos = PASSWORD_DEFAULT;
Comment thread
nanasess marked this conversation as resolved.
//MAGICハッシュワード決定
if ($_POST['db_skip'] && defined('AUTH_MAGIC')) {
$auth_magic = AUTH_MAGIC;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,11 @@
*/

/**
* SC_Utils::sfIsMatchHashPassword()のテストクラス.
* TODO まとめて実行する場合は定数の変更ができないためNG
* SC_Utils::sfIsMatchHashPassword()のテストクラス (AUTH_TYPE = PLAIN).
* AUTH_TYPE は定数のためまとめて実行できない. 個別実行が必要:
* data/vendor/bin/phpunit tests/class/util/SC_Utils/SC_Utils_sfIsMatchHashPassword_authTypePlainTest.php
*
* @author Hiroko Tamagawa
*
* @version $Id$
* @group auth_type_plain
*/
class SC_Utils_sfIsMatchHashPassword_authTypePlainTest extends Common_TestCase
{
Expand All @@ -46,33 +45,19 @@ protected function tearDown(): void
// parent::tearDown();
}

// ///////////////////////////////////////

// public function testSfIsMatchHashPassword_文字列が一致する場合_trueが返る()
// {
// $pass = 'ec-cube';
// $hashpass = 'ec-cube';

// $this->expected = TRUE;
// $this->actual = SC_Utils::sfIsMatchHashPassword($pass, $hashpass);

// $this->verify('パスワード文字列比較結果');
// }

// public function testSfIsMatchHashPassword_文字列が一致しない場合_falseが返る()
// {
// $pass = 'ec-cube';
// $hashpass = 'EC-cube';

// $this->expected = FALSE;
// $this->actual = SC_Utils::sfIsMatchHashPassword($pass, $hashpass);
public function testSfIsMatchHashPassword文字列が一致する場合Trueが返る()
{
$pass = 'ec-cube';
$hashpass = 'ec-cube';

// $this->verify('パスワード文字列比較結果');
// }
$this->assertTrue(SC_Utils::sfIsMatchHashPassword($pass, $hashpass, ''));
}

public function testDummyTest()
public function testSfIsMatchHashPassword文字列が一致しない場合Falseが返る()
{
// Warning が出るため空のテストを作成
$this->assertTrue(true);
$pass = 'ec-cube';
$hashpass = 'EC-cube';

$this->assertFalse(SC_Utils::sfIsMatchHashPassword($pass, $hashpass, ''));
}
}
Loading
Loading