| @@ -73,8 +73,31 @@ | ||
| 73 | 73 | return array(self::secret_key_hash($secret_key) => $defuse_key); |
| 74 | 74 | } |
| 75 | 75 | |
| 76 | 76 | /** |
| 77 | + * Checks whether a stored Defuse key can be loaded. | |
| 78 | + * | |
| 79 | + * @since 261006 | |
| 80 | + * | |
| 81 | + * @param string $defuse_key Stored ASCII-safe Defuse key. | |
| 82 | + * @return bool True for a valid key, else false. | |
| 83 | + */ | |
| 84 | + private static function valid_defuse_key($defuse_key) | |
| 85 | + { | |
| 86 | + if (!is_string($defuse_key) || !isset($defuse_key[0])) { | |
| 87 | + return false; | |
| 88 | + } | |
| 89 | + try { | |
| 90 | + Key::loadFromAsciiSafeString($defuse_key); | |
| 91 | + return true; | |
| 92 | + } catch (Throwable $Exception) { | |
| 93 | + return false; | |
| 94 | + } catch (Exception $Exception) { | |
| 95 | + return false; | |
| 96 | + } | |
| 97 | + } | |
| 98 | + | |
| 99 | + /** | |
| 77 | 100 | * Migrates legacy Defuse key mappings to hashed mappings. |
| 78 | 101 | * |
| 79 | 102 | * Preserves the existing Defuse keys while creating hashed mappings for |
| 80 | 103 | * the current default key, recovery history, and custom secret keys. |
| @@ -87,20 +110,33 @@ | ||
| 87 | 110 | public static function migrate_legacy_defuse_key_mappings() |
| 88 | 111 | { |
| 89 | 112 | $options = array(); |
| 90 | 113 | |
| 91 | - // Migrate the current default mapping without changing its Defuse key. | |
| 114 | + //261006.0424 Fingerprinted associations are authoritative; legacy data fills only missing or invalid keys, including direct upgrades. | |
| 92 | 115 | $legacy_mapping = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key']; |
| 93 | 116 | $converted_defuse_keys = self::convert_legacy_defuse_key_mapping($legacy_mapping); |
| 94 | - $defuse_keys = $converted_defuse_keys ? $converted_defuse_keys : $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']; | |
| 95 | - if ($converted_defuse_keys && $converted_defuse_keys !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']) { | |
| 117 | + $defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']; | |
| 118 | + foreach ($converted_defuse_keys as $_secret_key_hash => $_defuse_key) { | |
| 119 | + if (!isset($defuse_keys[$_secret_key_hash]) || !self::valid_defuse_key($defuse_keys[$_secret_key_hash])) { | |
| 120 | + $defuse_keys[$_secret_key_hash] = $_defuse_key; | |
| 121 | + } | |
| 122 | + } | |
| 123 | + if ($defuse_keys !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']) { | |
| 96 | 124 | $defuse_keys_update = $defuse_keys; |
| 97 | 125 | array_unshift($defuse_keys_update, 'update-signal'); |
| 98 | 126 | $options['ws_plugin__s2member_secret_key_to_defuse_key'] = $defuse_keys_update; |
| 99 | 127 | } |
| 100 | 128 | |
| 101 | - // Preserve default mappings as recovery-only history; normal decryption does not search it. | |
| 129 | + //261006.0424 Keep canonical recovery history ahead of legacy additions; conflicting keys remain available for explicit recovery, not normal lookup. | |
| 102 | 130 | $defuse_key_history = $defuse_keys ? array($defuse_keys) : array(); |
| 131 | + foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key_history'] as $_defuse_keys) { | |
| 132 | + if (is_array($_defuse_keys) && count($_defuse_keys) === 1 && !in_array($_defuse_keys, $defuse_key_history, true)) { | |
| 133 | + $defuse_key_history[] = $_defuse_keys; | |
| 134 | + } | |
| 135 | + } | |
| 136 | + if ($converted_defuse_keys && !in_array($converted_defuse_keys, $defuse_key_history, true)) { | |
| 137 | + $defuse_key_history[] = $converted_defuse_keys; | |
| 138 | + } | |
| 103 | 139 | foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key_history'] as $_legacy_mapping) { |
| 104 | 140 | $_defuse_keys = self::convert_legacy_defuse_key_mapping($_legacy_mapping); |
| 105 | 141 | if ($_defuse_keys && !in_array($_defuse_keys, $defuse_key_history, true)) { |
| 106 | 142 | $defuse_key_history[] = $_defuse_keys; |
| @@ -105,13 +141,8 @@ | ||
| 105 | 141 | if ($_defuse_keys && !in_array($_defuse_keys, $defuse_key_history, true)) { |
| 106 | 142 | $defuse_key_history[] = $_defuse_keys; |
| 107 | 143 | } |
| 108 | 144 | } |
| 109 | - foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key_history'] as $_defuse_keys) { | |
| 110 | - if (is_array($_defuse_keys) && count($_defuse_keys) === 1 && !in_array($_defuse_keys, $defuse_key_history, true)) { | |
| 111 | - $defuse_key_history[] = $_defuse_keys; | |
| 112 | - } | |
| 113 | - } | |
| 114 | 145 | $defuse_key_history = array_slice($defuse_key_history, 0, 10); |
| 115 | 146 | if ($defuse_key_history !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key_history']) { |
| 116 | 147 | $defuse_key_history_update = $defuse_key_history; |
| 117 | 148 | array_unshift($defuse_key_history_update, 'update-signal'); |
| @@ -123,10 +154,13 @@ | ||
| 123 | 154 | foreach ($GLOBALS['WS_PLUGIN__']['s2member']['o']['def_custom_combo_encryption_keys'] as $_secret_key => $_defuse_key) { |
| 124 | 155 | $_secret_key = (string) $_secret_key; |
| 125 | 156 | $_defuse_key = (string) $_defuse_key; |
| 126 | 157 | |
| 127 | - if (isset($_secret_key[0]) && isset($_defuse_key[0])) { | |
| 128 | - $custom_defuse_keys[self::secret_key_hash($_secret_key)] = $_defuse_key; | |
| 158 | + if (isset($_secret_key[0]) && self::valid_defuse_key($_defuse_key)) { | |
| 159 | + $_secret_key_hash = self::secret_key_hash($_secret_key); | |
| 160 | + if (!isset($custom_defuse_keys[$_secret_key_hash]) || !self::valid_defuse_key($custom_defuse_keys[$_secret_key_hash])) { | |
| 161 | + $custom_defuse_keys[$_secret_key_hash] = $_defuse_key; | |
| 162 | + } | |
| 129 | 163 | } |
| 130 | 164 | } |
| 131 | 165 | if ($custom_defuse_keys !== $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key']) { |
| 132 | 166 | array_unshift($custom_defuse_keys, 'update-signal'); |
| @@ -156,29 +190,29 @@ | ||
| 156 | 190 | $secret_key = c_ws_plugin__s2member_utils_encryption::key($secret_key); |
| 157 | 191 | $secret_key_hash = self::secret_key_hash($secret_key); |
| 158 | 192 | |
| 159 | 193 | if ($has_custom_secret_key) { |
| 160 | - // Prefer the legacy custom mapping during the compatibility release. | |
| 194 | + //261006.0424 Prefer valid fingerprinted keys while retaining legacy lookup for installations not yet migrated. | |
| 195 | + $custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key']; | |
| 196 | + if ($secret_key_hash && isset($custom_defuse_keys[$secret_key_hash]) && self::valid_defuse_key($custom_defuse_keys[$secret_key_hash])) { | |
| 197 | + return $custom_defuse_keys[$secret_key_hash]; | |
| 198 | + } | |
| 161 | 199 | $legacy_custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_custom_combo_encryption_keys']; |
| 162 | - if ($secret_key && !empty($legacy_custom_defuse_keys[$secret_key])) { | |
| 200 | + if ($secret_key && isset($legacy_custom_defuse_keys[$secret_key]) && self::valid_defuse_key($legacy_custom_defuse_keys[$secret_key])) { | |
| 163 | 201 | return $legacy_custom_defuse_keys[$secret_key]; |
| 164 | 202 | } |
| 165 | - // Fall back to the hashed custom mapping prepared for later releases. | |
| 166 | - $custom_defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['custom_secret_key_to_defuse_key']; | |
| 167 | - if ($secret_key_hash && !empty($custom_defuse_keys[$secret_key_hash])) { | |
| 168 | - return $custom_defuse_keys[$secret_key_hash]; | |
| 169 | - } | |
| 170 | 203 | return ''; |
| 171 | 204 | } |
| 172 | - // Prefer the legacy default mapping during the compatibility release. | |
| 205 | + $defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']; | |
| 206 | + if ($secret_key_hash && isset($defuse_keys[$secret_key_hash]) && self::valid_defuse_key($defuse_keys[$secret_key_hash])) { | |
| 207 | + return $defuse_keys[$secret_key_hash]; | |
| 208 | + } | |
| 173 | 209 | $legacy_mapping = $GLOBALS['WS_PLUGIN__']['s2member']['o']['def_combo_encryption_key']; |
| 174 | 210 | if ($secret_key && $legacy_mapping && strpos($legacy_mapping, $secret_key."\n") === 0) { |
| 175 | - return substr($legacy_mapping, strlen($secret_key) + 1); | |
| 211 | + $legacy_defuse_key = substr($legacy_mapping, strlen($secret_key) + 1); | |
| 212 | + return self::valid_defuse_key($legacy_defuse_key) ? $legacy_defuse_key : ''; | |
| 176 | 213 | } |
| 177 | - // Fall back to the hashed default mapping prepared for later releases. | |
| 178 | - $defuse_keys = $GLOBALS['WS_PLUGIN__']['s2member']['o']['secret_key_to_defuse_key']; | |
| 179 | - | |
| 180 | - return ($secret_key_hash && !empty($defuse_keys[$secret_key_hash])) ? $defuse_keys[$secret_key_hash] : ''; | |
| 214 | + return ''; | |
| 181 | 215 | } |
| 182 | 216 | |
| 183 | 217 | /** |
| 184 | 218 | * Creates and stores a new Defuse encryption key. |