Defender Security – Malware Scanner, Login Security & Firewall - Version 3.3.3

Version Description

( 2022-10-20 ) =

  • Enhance: 2FA flow for secret keys
Download this release

Release Info

Developer BigTonny
Plugin Icon 128x128 Defender Security – Malware Scanner, Login Security & Firewall
Version 3.3.3
Comparing to
See all releases

Code changes from version 3.3.2 to 3.3.3

languages/wpdef-default.pot CHANGED
@@ -6,9 +6,9 @@
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
- "Project-Id-Version: wp-defender 3.3.2\n"
10
  "Report-Msgid-Bugs-To: \n"
11
- "POT-Creation-Date: 2022-09-29 12:54+0300\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,11 +17,11 @@ msgstr ""
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
- #: free/bootstrap.php:279
21
  msgid "Get Secure!"
22
  msgstr ""
23
 
24
- #: free/bootstrap.php:305
25
  msgid ""
26
  "You're awesome for installing Defender! Are you interested in how to make "
27
  "the most of this plugin? We've collected all the best security resources we "
@@ -1090,7 +1090,7 @@ msgstr ""
1090
  msgid "password reset"
1091
  msgstr ""
1092
 
1093
- #: src/component/backup-settings.php:529 src/upgrader.php:649
1094
  msgid "Basic Config"
1095
  msgstr ""
1096
 
@@ -1128,8 +1128,8 @@ msgstr ""
1128
  #: src/controller/password-protection.php:290
1129
  #: src/controller/password-protection.php:296 src/controller/recaptcha.php:1017
1130
  #: src/controller/scan.php:661 src/controller/scan.php:694
1131
- #: src/controller/security-headers.php:167 src/controller/two-factor.php:1134
1132
- #: src/controller/two-factor.php:1146
1133
  #: front/src/module/dashboard/component/advanced-tools.vue:30
1134
  #: front/src/module/dashboard/component/advanced-tools.vue:52
1135
  #: front/src/module/dashboard/component/advanced-tools.vue:93
@@ -1147,8 +1147,8 @@ msgstr ""
1147
  #: src/controller/mask-login.php:757 src/controller/password-protection.php:278
1148
  #: src/controller/password-protection.php:295 src/controller/recaptcha.php:1017
1149
  #: src/controller/scan.php:659 src/controller/scan.php:693
1150
- #: src/controller/security-headers.php:167 src/controller/two-factor.php:1134
1151
- #: src/controller/two-factor.php:1146
1152
  #: front/src/module/dashboard/component/advanced-tools.vue:29
1153
  #: front/src/module/dashboard/component/advanced-tools.vue:53
1154
  #: front/src/module/dashboard/component/advanced-tools.vue:92
@@ -1201,7 +1201,7 @@ msgid "Firewall"
1201
  msgstr ""
1202
 
1203
  #: src/component/backup-settings.php:1195 src/controller/two-factor.php:68
1204
- #: src/controller/two-factor.php:1415
1205
  msgid "2FA"
1206
  msgstr ""
1207
 
@@ -1264,6 +1264,18 @@ msgstr ""
1264
  msgid "Allow all"
1265
  msgstr ""
1266
 
 
 
 
 
 
 
 
 
 
 
 
 
1267
  #. translators: %s: separator
1268
  #: src/component/feature-modal.php:31
1269
  msgid "What's new in Defender?"
@@ -1979,15 +1991,15 @@ msgstr ""
1979
  msgid "100"
1980
  msgstr ""
1981
 
1982
- #: src/component/two-fa.php:447
1983
  msgid "The user is invalid."
1984
  msgstr ""
1985
 
1986
- #: src/component/two-fa.php:452
1987
  msgid "Your token is invalid."
1988
  msgstr ""
1989
 
1990
- #: src/component/two-fa.php:477 src/controller/two-factor.php:968
1991
  #: src/view/two-fa/user-options.php:12
1992
  #: front/src/module/dashboard/component/preset-config.vue:123
1993
  #: front/src/module/dashboard/component/two-fa.vue:6
@@ -1997,27 +2009,27 @@ msgstr ""
1997
  msgid "Two-Factor Authentication"
1998
  msgstr ""
1999
 
2000
- #: src/component/two-fa.php:501
2001
  msgid "ERROR: Cheatin&#8217; uh?"
2002
  msgstr ""
2003
 
2004
- #: src/component/two-fa.php:576
2005
  #, php-format
2006
  msgid "Lockout occurred: Too many failed 2fa attempts for %s method."
2007
  msgstr ""
2008
 
2009
- #: src/component/two-fa.php:589
2010
  msgid "INVALID CODE: "
2011
  msgstr ""
2012
 
2013
- #: src/component/two-fa.php:590
2014
  msgid ""
2015
  "The two-factor authentication code you entered is incorrect or has expired. "
2016
  "Please try again."
2017
  msgstr ""
2018
 
2019
  #. translators: %s: count
2020
- #: src/component/two-fa.php:594
2021
  #, php-format
2022
  msgid "You have %s login attempt remaining."
2023
  msgstr ""
@@ -2027,7 +2039,7 @@ msgid "Generate non-expirable backup codes that can be used to log in once."
2027
  msgstr ""
2028
 
2029
  #: src/component/two-factor/providers/backup-codes.php:77
2030
- #: src/controller/two-factor.php:1293
2031
  msgid "Each backup code can only be used to log in once."
2032
  msgstr ""
2033
 
@@ -2045,7 +2057,7 @@ msgstr ""
2045
 
2046
  #: src/component/two-factor/providers/backup-codes.php:138
2047
  #: src/component/two-factor/providers/fallback-email.php:107
2048
- #: src/component/two-factor/providers/totp.php:118
2049
  msgid "Authenticate"
2050
  msgstr ""
2051
 
@@ -2062,7 +2074,7 @@ msgid "Generate Backup Codes"
2062
  msgstr ""
2063
 
2064
  #: src/component/two-factor/providers/backup-codes.php:233
2065
- #: src/controller/two-factor.php:1292
2066
  msgid "Get New Codes"
2067
  msgstr ""
2068
 
@@ -2104,41 +2116,41 @@ msgstr ""
2104
  msgid "ERROR: Invalid passcode."
2105
  msgstr ""
2106
 
2107
- #: src/component/two-factor/providers/totp.php:87
2108
  msgid "TOTP Authenticator App"
2109
  msgstr ""
2110
 
2111
- #: src/component/two-factor/providers/totp.php:94
2112
  msgid "TOTP Authentication"
2113
  msgstr ""
2114
 
2115
- #: src/component/two-factor/providers/totp.php:101
2116
  msgid "TOTP"
2117
  msgstr ""
2118
 
2119
  #. translators: %s: style class
2120
- #: src/component/two-factor/providers/totp.php:141
2121
  #, php-format
2122
  msgid ""
2123
  "<button type=\"button\" class=\"button reset-totp-keys button-secondary hide-"
2124
  "if-no-js\" %s>Reset Keys</button>"
2125
  msgstr ""
2126
 
2127
- #: src/component/two-factor/providers/totp.php:146
2128
  msgid "TOTP Authentication method is active for this site"
2129
  msgstr ""
2130
 
2131
- #: src/component/two-factor/providers/totp.php:147
2132
  msgid "Use an authenticator app to sign in with a separate passcode."
2133
  msgstr ""
2134
 
2135
- #: src/component/two-factor/providers/totp.php:224
2136
  #: src/controller/two-factor.php:401
2137
  msgid "Whoops, the passcode you entered was incorrect or expired."
2138
  msgstr ""
2139
 
2140
  #: src/component/two-factor/providers/webauthn.php:46
2141
- #: src/controller/two-factor.php:1096
2142
  msgid "Beta"
2143
  msgstr ""
2144
 
@@ -2298,7 +2310,7 @@ msgstr ""
2298
  #: src/controller/firewall.php:144 src/controller/main-setting.php:97
2299
  #: src/controller/mask-login.php:312 src/controller/password-protection.php:218
2300
  #: src/controller/password-reset.php:198 src/controller/scan.php:361
2301
- #: src/controller/security-headers.php:74 src/controller/two-factor.php:878
2302
  #: src/traits/setting.php:21
2303
  msgid "Your settings have been updated."
2304
  msgstr ""
@@ -2946,24 +2958,24 @@ msgstr ""
2946
  msgid "You have logged in successfully."
2947
  msgstr ""
2948
 
2949
- #: src/controller/two-factor.php:638
2950
  msgid "Please input a valid OTP code."
2951
  msgstr ""
2952
 
2953
- #: src/controller/two-factor.php:668
2954
  msgid "Your OTP code is incorrect. Please try again."
2955
  msgstr ""
2956
 
2957
- #: src/controller/two-factor.php:980
2958
  msgid "Test email has been sent to your email."
2959
  msgstr ""
2960
 
2961
- #: src/controller/two-factor.php:985
2962
  msgid "Test email failed."
2963
  msgstr ""
2964
 
2965
  #. translators: %s: link
2966
- #: src/controller/two-factor.php:1099
2967
  #, php-format
2968
  msgid ""
2969
  "Web Authentication is now available. <a target=\"_blank\" href=\"%s\">Click "
@@ -2971,26 +2983,26 @@ msgid ""
2971
  msgstr ""
2972
 
2973
  #. translators: %s: count
2974
- #: src/controller/two-factor.php:1289
2975
  #, php-format
2976
  msgid "2FA Backup Codes for %s:"
2977
  msgstr ""
2978
 
2979
- #: src/controller/two-factor.php:1318
2980
  msgid "Reset two factor"
2981
  msgstr ""
2982
 
2983
  #. translators: %s: URL to regenerate code
2984
- #: src/controller/two-factor.php:1363
2985
  #, php-format
2986
  msgid "Two factor authentication has been reset for <b>%s.</b>"
2987
  msgstr ""
2988
 
2989
- #: src/controller/two-factor.php:1388 src/controller/two-factor.php:1389
2990
  msgid "Save changes"
2991
  msgstr ""
2992
 
2993
- #: src/controller/two-factor.php:1441
2994
  msgid "Two-Factor settings updated successfully."
2995
  msgstr ""
2996
 
@@ -4225,7 +4237,7 @@ msgstr ""
4225
  msgid "Hub"
4226
  msgstr ""
4227
 
4228
- #: src/upgrader.php:648
4229
  msgid "Basic config"
4230
  msgstr ""
4231
 
@@ -4495,31 +4507,31 @@ msgstr ""
4495
  msgid "2. Scan the QR code or enter the key"
4496
  msgstr ""
4497
 
4498
- #: src/view/two-fa/providers/totp-disabled.php:31
4499
  msgid ""
4500
  "Open the authenticator app, and scan the QR code below or manually enter the "
4501
  "setup key to add your new site."
4502
  msgstr ""
4503
 
4504
- #: src/view/two-fa/providers/totp-disabled.php:43
4505
  msgid "Copy"
4506
  msgstr ""
4507
 
4508
- #: src/view/two-fa/providers/totp-disabled.php:48
4509
  msgid "3. Enter passcode"
4510
  msgstr ""
4511
 
4512
- #: src/view/two-fa/providers/totp-disabled.php:53
4513
  msgid ""
4514
  "Enter the 6 digit passcode that is shown on your device into the input field "
4515
  "below and hit \"Verify\"."
4516
  msgstr ""
4517
 
4518
- #: src/view/two-fa/providers/totp-disabled.php:61
4519
  msgid "Enter passcode"
4520
  msgstr ""
4521
 
4522
- #: src/view/two-fa/providers/totp-disabled.php:63
4523
  msgid "Verify"
4524
  msgstr ""
4525
 
6
  #, fuzzy
7
  msgid ""
8
  msgstr ""
9
+ "Project-Id-Version: wp-defender 3.3.3\n"
10
  "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2022-10-20 05:44+0000\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
+ #: free/bootstrap.php:281
21
  msgid "Get Secure!"
22
  msgstr ""
23
 
24
+ #: free/bootstrap.php:307
25
  msgid ""
26
  "You're awesome for installing Defender! Are you interested in how to make "
27
  "the most of this plugin? We've collected all the best security resources we "
1090
  msgid "password reset"
1091
  msgstr ""
1092
 
1093
+ #: src/component/backup-settings.php:529 src/upgrader.php:650
1094
  msgid "Basic Config"
1095
  msgstr ""
1096
 
1128
  #: src/controller/password-protection.php:290
1129
  #: src/controller/password-protection.php:296 src/controller/recaptcha.php:1017
1130
  #: src/controller/scan.php:661 src/controller/scan.php:694
1131
+ #: src/controller/security-headers.php:167 src/controller/two-factor.php:1155
1132
+ #: src/controller/two-factor.php:1167
1133
  #: front/src/module/dashboard/component/advanced-tools.vue:30
1134
  #: front/src/module/dashboard/component/advanced-tools.vue:52
1135
  #: front/src/module/dashboard/component/advanced-tools.vue:93
1147
  #: src/controller/mask-login.php:757 src/controller/password-protection.php:278
1148
  #: src/controller/password-protection.php:295 src/controller/recaptcha.php:1017
1149
  #: src/controller/scan.php:659 src/controller/scan.php:693
1150
+ #: src/controller/security-headers.php:167 src/controller/two-factor.php:1155
1151
+ #: src/controller/two-factor.php:1167
1152
  #: front/src/module/dashboard/component/advanced-tools.vue:29
1153
  #: front/src/module/dashboard/component/advanced-tools.vue:53
1154
  #: front/src/module/dashboard/component/advanced-tools.vue:92
1201
  msgstr ""
1202
 
1203
  #: src/component/backup-settings.php:1195 src/controller/two-factor.php:68
1204
+ #: src/controller/two-factor.php:1436
1205
  msgid "2FA"
1206
  msgstr ""
1207
 
1264
  msgid "Allow all"
1265
  msgstr ""
1266
 
1267
+ #: src/component/crypt.php:140 src/component/crypt.php:152
1268
+ msgid "Please re-setup 2FA TOTP method again."
1269
+ msgstr ""
1270
+
1271
+ #: src/component/crypt.php:213
1272
+ msgid "The Defender file with the random key does not exist."
1273
+ msgstr ""
1274
+
1275
+ #: src/component/crypt.php:224
1276
+ msgid "The Defender file with the random key is incorrect."
1277
+ msgstr ""
1278
+
1279
  #. translators: %s: separator
1280
  #: src/component/feature-modal.php:31
1281
  msgid "What's new in Defender?"
1991
  msgid "100"
1992
  msgstr ""
1993
 
1994
+ #: src/component/two-fa.php:451
1995
  msgid "The user is invalid."
1996
  msgstr ""
1997
 
1998
+ #: src/component/two-fa.php:456
1999
  msgid "Your token is invalid."
2000
  msgstr ""
2001
 
2002
+ #: src/component/two-fa.php:481 src/controller/two-factor.php:983
2003
  #: src/view/two-fa/user-options.php:12
2004
  #: front/src/module/dashboard/component/preset-config.vue:123
2005
  #: front/src/module/dashboard/component/two-fa.vue:6
2009
  msgid "Two-Factor Authentication"
2010
  msgstr ""
2011
 
2012
+ #: src/component/two-fa.php:505
2013
  msgid "ERROR: Cheatin&#8217; uh?"
2014
  msgstr ""
2015
 
2016
+ #: src/component/two-fa.php:582
2017
  #, php-format
2018
  msgid "Lockout occurred: Too many failed 2fa attempts for %s method."
2019
  msgstr ""
2020
 
2021
+ #: src/component/two-fa.php:595
2022
  msgid "INVALID CODE: "
2023
  msgstr ""
2024
 
2025
+ #: src/component/two-fa.php:596
2026
  msgid ""
2027
  "The two-factor authentication code you entered is incorrect or has expired. "
2028
  "Please try again."
2029
  msgstr ""
2030
 
2031
  #. translators: %s: count
2032
+ #: src/component/two-fa.php:600
2033
  #, php-format
2034
  msgid "You have %s login attempt remaining."
2035
  msgstr ""
2039
  msgstr ""
2040
 
2041
  #: src/component/two-factor/providers/backup-codes.php:77
2042
+ #: src/controller/two-factor.php:1314
2043
  msgid "Each backup code can only be used to log in once."
2044
  msgstr ""
2045
 
2057
 
2058
  #: src/component/two-factor/providers/backup-codes.php:138
2059
  #: src/component/two-factor/providers/fallback-email.php:107
2060
+ #: src/component/two-factor/providers/totp.php:128
2061
  msgid "Authenticate"
2062
  msgstr ""
2063
 
2074
  msgstr ""
2075
 
2076
  #: src/component/two-factor/providers/backup-codes.php:233
2077
+ #: src/controller/two-factor.php:1313
2078
  msgid "Get New Codes"
2079
  msgstr ""
2080
 
2116
  msgid "ERROR: Invalid passcode."
2117
  msgstr ""
2118
 
2119
+ #: src/component/two-factor/providers/totp.php:97
2120
  msgid "TOTP Authenticator App"
2121
  msgstr ""
2122
 
2123
+ #: src/component/two-factor/providers/totp.php:104
2124
  msgid "TOTP Authentication"
2125
  msgstr ""
2126
 
2127
+ #: src/component/two-factor/providers/totp.php:111
2128
  msgid "TOTP"
2129
  msgstr ""
2130
 
2131
  #. translators: %s: style class
2132
+ #: src/component/two-factor/providers/totp.php:151
2133
  #, php-format
2134
  msgid ""
2135
  "<button type=\"button\" class=\"button reset-totp-keys button-secondary hide-"
2136
  "if-no-js\" %s>Reset Keys</button>"
2137
  msgstr ""
2138
 
2139
+ #: src/component/two-factor/providers/totp.php:156
2140
  msgid "TOTP Authentication method is active for this site"
2141
  msgstr ""
2142
 
2143
+ #: src/component/two-factor/providers/totp.php:157
2144
  msgid "Use an authenticator app to sign in with a separate passcode."
2145
  msgstr ""
2146
 
2147
+ #: src/component/two-factor/providers/totp.php:249
2148
  #: src/controller/two-factor.php:401
2149
  msgid "Whoops, the passcode you entered was incorrect or expired."
2150
  msgstr ""
2151
 
2152
  #: src/component/two-factor/providers/webauthn.php:46
2153
+ #: src/controller/two-factor.php:1117
2154
  msgid "Beta"
2155
  msgstr ""
2156
 
2310
  #: src/controller/firewall.php:144 src/controller/main-setting.php:97
2311
  #: src/controller/mask-login.php:312 src/controller/password-protection.php:218
2312
  #: src/controller/password-reset.php:198 src/controller/scan.php:361
2313
+ #: src/controller/security-headers.php:74 src/controller/two-factor.php:893
2314
  #: src/traits/setting.php:21
2315
  msgid "Your settings have been updated."
2316
  msgstr ""
2958
  msgid "You have logged in successfully."
2959
  msgstr ""
2960
 
2961
+ #: src/controller/two-factor.php:645
2962
  msgid "Please input a valid OTP code."
2963
  msgstr ""
2964
 
2965
+ #: src/controller/two-factor.php:683
2966
  msgid "Your OTP code is incorrect. Please try again."
2967
  msgstr ""
2968
 
2969
+ #: src/controller/two-factor.php:995
2970
  msgid "Test email has been sent to your email."
2971
  msgstr ""
2972
 
2973
+ #: src/controller/two-factor.php:1000
2974
  msgid "Test email failed."
2975
  msgstr ""
2976
 
2977
  #. translators: %s: link
2978
+ #: src/controller/two-factor.php:1120
2979
  #, php-format
2980
  msgid ""
2981
  "Web Authentication is now available. <a target=\"_blank\" href=\"%s\">Click "
2983
  msgstr ""
2984
 
2985
  #. translators: %s: count
2986
+ #: src/controller/two-factor.php:1310
2987
  #, php-format
2988
  msgid "2FA Backup Codes for %s:"
2989
  msgstr ""
2990
 
2991
+ #: src/controller/two-factor.php:1339
2992
  msgid "Reset two factor"
2993
  msgstr ""
2994
 
2995
  #. translators: %s: URL to regenerate code
2996
+ #: src/controller/two-factor.php:1384
2997
  #, php-format
2998
  msgid "Two factor authentication has been reset for <b>%s.</b>"
2999
  msgstr ""
3000
 
3001
+ #: src/controller/two-factor.php:1409 src/controller/two-factor.php:1410
3002
  msgid "Save changes"
3003
  msgstr ""
3004
 
3005
+ #: src/controller/two-factor.php:1462
3006
  msgid "Two-Factor settings updated successfully."
3007
  msgstr ""
3008
 
4237
  msgid "Hub"
4238
  msgstr ""
4239
 
4240
+ #: src/upgrader.php:649
4241
  msgid "Basic config"
4242
  msgstr ""
4243
 
4507
  msgid "2. Scan the QR code or enter the key"
4508
  msgstr ""
4509
 
4510
+ #: src/view/two-fa/providers/totp-disabled.php:32
4511
  msgid ""
4512
  "Open the authenticator app, and scan the QR code below or manually enter the "
4513
  "setup key to add your new site."
4514
  msgstr ""
4515
 
4516
+ #: src/view/two-fa/providers/totp-disabled.php:44
4517
  msgid "Copy"
4518
  msgstr ""
4519
 
4520
+ #: src/view/two-fa/providers/totp-disabled.php:52
4521
  msgid "3. Enter passcode"
4522
  msgstr ""
4523
 
4524
+ #: src/view/two-fa/providers/totp-disabled.php:57
4525
  msgid ""
4526
  "Enter the 6 digit passcode that is shown on your device into the input field "
4527
  "below and hit \"Verify\"."
4528
  msgstr ""
4529
 
4530
+ #: src/view/two-fa/providers/totp-disabled.php:65
4531
  msgid "Enter passcode"
4532
  msgstr ""
4533
 
4534
+ #: src/view/two-fa/providers/totp-disabled.php:67
4535
  msgid "Verify"
4536
  msgstr ""
4537
 
readme.txt CHANGED
@@ -1,13 +1,13 @@
1
  === Defender Security - Malware Scanner, Login Security & Firewall ===
2
  Plugin Name: Defender Security - Malware Scanner, Login Security & Firewall
3
- Version: 3.3.2
4
  Author: WPMU DEV
5
  Author URI: https://wpmudev.com/
6
  Contributors: WPMUDEV
7
  Tags: security plugin, security, firewall, malware, malware scanner, antivirus, ip blocking, login security, brute force attacks, limit login attempts, custom login url, activity log, audit logs, block hackers, two-factor authentication, 2fa, hack, captcha, webauthn, authentication, fido2, fingerprint, face verification, yubikey, USB keys, woocommerce
8
  Requires at least: 5.2
9
- Tested up to: 6.0.2
10
- Stable tag: 3.3.2
11
  Requires PHP: 7.2.0
12
  License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13
 
@@ -247,6 +247,10 @@ Please open a new thread in Defender’s [support forum](https://wordpress.org/s
247
 
248
  == Changelog ==
249
 
 
 
 
 
250
  = 3.3.2 ( 2022-09-29 ) =
251
 
252
  - Fix: Encrypt 2FA secret keys
1
  === Defender Security - Malware Scanner, Login Security & Firewall ===
2
  Plugin Name: Defender Security - Malware Scanner, Login Security & Firewall
3
+ Version: 3.3.3
4
  Author: WPMU DEV
5
  Author URI: https://wpmudev.com/
6
  Contributors: WPMUDEV
7
  Tags: security plugin, security, firewall, malware, malware scanner, antivirus, ip blocking, login security, brute force attacks, limit login attempts, custom login url, activity log, audit logs, block hackers, two-factor authentication, 2fa, hack, captcha, webauthn, authentication, fido2, fingerprint, face verification, yubikey, USB keys, woocommerce
8
  Requires at least: 5.2
9
+ Tested up to: 6.1
10
+ Stable tag: 3.3.3
11
  Requires PHP: 7.2.0
12
  License: GPL v2 - http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
13
 
247
 
248
  == Changelog ==
249
 
250
+ = 3.3.3 ( 2022-10-20 ) =
251
+
252
+ - Enhance: 2FA flow for secret keys
253
+
254
  = 3.3.2 ( 2022-09-29 ) =
255
 
256
  - Fix: Encrypt 2FA secret keys
src/bootstrap.php CHANGED
@@ -39,6 +39,8 @@ class Bootstrap {
39
  $this->create_database_tables();
40
  $this->set_free_installation_timestamp();
41
  $this->on_activation();
 
 
42
  }
43
 
44
  /**
39
  $this->create_database_tables();
40
  $this->set_free_installation_timestamp();
41
  $this->on_activation();
42
+ // Create a file with a random key if it doesn't exist.
43
+ ( new \WP_Defender\Component\Crypt() )->create_key_file();
44
  }
45
 
46
  /**
src/component/crypt.php CHANGED
@@ -3,7 +3,17 @@ declare( strict_types = 1 );
3
 
4
  namespace WP_Defender\Component;
5
 
 
 
 
 
 
 
 
 
 
6
  class Crypt extends \Calotes\Base\Component {
 
7
 
8
  /**
9
  * Generates cryptographically secure pseudo-random bytes.
@@ -98,56 +108,155 @@ class Crypt extends \Calotes\Base\Component {
98
  }
99
 
100
  /**
101
- * @param string $plaintext
 
 
102
  * @param string $key
103
  *
104
  * @return string
 
105
  */
106
- private static function encrypt_data( $plaintext, $key ) {
107
- $cipher = 'aes-256-cbc';
108
- $iv_len = openssl_cipher_iv_length( $cipher );
109
- $iv = self::random_bytes( $iv_len );
110
- $ciphertext_raw = openssl_encrypt( $plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv );
111
- $hmac = hash_hmac( 'sha256', $ciphertext_raw, $key, true );
112
-
113
- return base64_encode( $iv . $hmac . $ciphertext_raw );
114
  }
115
 
116
  /**
117
- * @param string $encrypt_data
 
 
118
  * @param string $key
119
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  * @return string
121
  */
122
- private static function decrypt_data( $encrypt_data, $key ) {
123
- $str = base64_decode( $encrypt_data );
124
- $cipher = 'aes-256-cbc';
125
- $iv_len = openssl_cipher_iv_length( $cipher );
126
- $iv = substr( $str, 0, $iv_len );
127
- $ciphertext_raw = substr( $str, $iv_len + 32 );
128
-
129
- return openssl_decrypt( $ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv );
130
  }
131
 
132
  /**
 
 
133
  * @param string $data
134
  *
135
- * @return string
 
136
  */
137
- public static function get_decrypted_string( $data ): string {
138
- $key = file_get_contents( __DIR__ . '/def.key' );
 
 
 
139
 
140
- return false !== $key ? self::decrypt_data( $data, $key ) : '';
141
  }
142
 
143
  /**
 
 
144
  * @param string $data
145
  *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  * @return string
147
  */
148
- public static function get_encrypted_string( $data ): string {
149
- $key = file_get_contents( __DIR__ . '/def.key' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
- return false !== $key ? self::encrypt_data( $data, $key ) : '';
 
 
 
 
 
 
 
152
  }
153
  }
3
 
4
  namespace WP_Defender\Component;
5
 
6
+ use WP_Defender\Traits\IO;
7
+ use WP_Error;
8
+
9
+ /**
10
+ * Class Crypt.
11
+ *
12
+ * @since 3.3.1
13
+ * @package WP_Defender\Component
14
+ */
15
  class Crypt extends \Calotes\Base\Component {
16
+ use IO;
17
 
18
  /**
19
  * Generates cryptographically secure pseudo-random bytes.
108
  }
109
 
110
  /**
111
+ * Encrypt data.
112
+ *
113
+ * @param string $value
114
  * @param string $key
115
  *
116
  * @return string
117
+ * @throws \SodiumException
118
  */
119
+ private static function encrypt( $value, $key ): string {
120
+ $key = base64_decode( $key );
121
+ $nonce = self::random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES );
122
+ $ciphertext = sodium_crypto_secretbox( $value, $nonce, $key );
123
+
124
+ return base64_encode( $nonce . $ciphertext );
 
 
125
  }
126
 
127
  /**
128
+ * Decrypt encrypted data.
129
+ *
130
+ * @param string $encoded_value
131
  * @param string $key
132
  *
133
+ * @return string|WP_Error
134
+ * @throws \SodiumException
135
+ */
136
+ private static function decrypt( $encoded_value, $key ) {
137
+ if ( ! $encoded_value || '' === $key ) {
138
+ return new WP_Error(
139
+ Error_Code::TFA_DECRYPT_ERROR,
140
+ __( 'Please re-setup 2FA TOTP method again.', 'wpdef' )
141
+ );
142
+ }
143
+ $key = base64_decode( $key );
144
+ $decoded = base64_decode( $encoded_value );
145
+ $nonce = mb_substr( $decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit' );
146
+ $ciphertext = mb_substr( $decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit' );
147
+
148
+ $decrypted = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key );
149
+ if ( false === $decrypted) {
150
+ return new WP_Error(
151
+ Error_Code::TFA_DECRYPT_ERROR,
152
+ __( 'Please re-setup 2FA TOTP method again.', 'wpdef' )
153
+ );
154
+ }
155
+
156
+ return $decrypted;
157
+ }
158
+
159
+ /**
160
+ * Get the path to a file with a random key. This is used for 2FA TOTP.
161
+ *
162
  * @return string
163
  */
164
+ public static function get_path_to_key_file() {
165
+ return wp_normalize_path( WP_CONTENT_DIR ) . DIRECTORY_SEPARATOR . 'wp-defender-secrets.php';
 
 
 
 
 
 
166
  }
167
 
168
  /**
169
+ * Get decrypted data.
170
+ *
171
  * @param string $data
172
  *
173
+ * @return string|WP_Error
174
+ * @throws \SodiumException
175
  */
176
+ public static function get_decrypted_data( $data ) {
177
+ $key = self::get_random_key();
178
+ if ( is_wp_error( $key ) ) {
179
+ return $key;
180
+ }
181
 
182
+ return self::decrypt( $data, $key );
183
  }
184
 
185
  /**
186
+ * Get encrypted data.
187
+ *
188
  * @param string $data
189
  *
190
+ * @return string|WP_Error
191
+ * @throws \SodiumException
192
+ */
193
+ public static function get_encrypted_data( $data ) {
194
+ $key = self::get_random_key();
195
+ if ( is_wp_error( $key ) ) {
196
+ return $key;
197
+ }
198
+
199
+ return self::encrypt( $data, $key );
200
+ }
201
+
202
+ /**
203
+ * Get random key.
204
+ *
205
+ * @return string|WP_Error
206
+ * @throws \SodiumException
207
+ */
208
+ private static function get_random_key() {
209
+ $file = self::get_path_to_key_file();
210
+ if ( ! file_exists( $file ) ) {
211
+ return new WP_Error(
212
+ Error_Code::IS_EMPTY,
213
+ __( 'The Defender file with the random key does not exist.', 'wpdef' )
214
+ );
215
+ }
216
+
217
+ if ( ! defined( 'WP_DEFENDER_TOTP_KEY' ) ) {
218
+ require_once $file;
219
+ }
220
+
221
+ if ( '{{__REPLACE_CODE__}}' !== constant( 'WP_DEFENDER_TOTP_KEY' ) ) {
222
+ return WP_DEFENDER_TOTP_KEY;
223
+ } else {
224
+ return new WP_Error( Error_Code::INVALID, __( 'The Defender file with the random key is incorrect.', 'wpdef' ) );
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Generate a random key.
230
+ *
231
  * @return string
232
  */
233
+ protected function generate_random_key(): string {
234
+ return base64_encode( sodium_crypto_secretbox_keygen() );
235
+ }
236
+
237
+ /**
238
+ * Create a file with a random key.
239
+ *
240
+ * @return bool
241
+ */
242
+ public function create_key_file(): bool {
243
+ $to = self::get_path_to_key_file();
244
+ if ( ! file_exists( $to ) ) {
245
+ // Move a template file to WP_CONTENT and replace the file content.
246
+ $template_file = WP_DEFENDER_DIR . 'src' . DIRECTORY_SEPARATOR . 'component' . DIRECTORY_SEPARATOR
247
+ . 'wp-defender-sample.php';
248
+ if ( copy( $template_file, $to ) ) {
249
+ $content = file_get_contents( $to );
250
+ if ( false !== strpos( $content, '{{__REPLACE_CODE__}}' ) ) {
251
+ $new_content = str_replace( '{{__REPLACE_CODE__}}', $this->generate_random_key(), $content );
252
 
253
+ return (bool) file_put_contents( $to, $new_content, LOCK_EX );
254
+ }
255
+ }
256
+ // The file was not copied.
257
+ return false;
258
+ }
259
+ // Everything is fine. The file exists.
260
+ return true;
261
  }
262
  }
src/component/error-code.php CHANGED
@@ -6,14 +6,15 @@
6
  namespace WP_Defender\Component;
7
 
8
  class Error_Code {
9
- public const NOT_WRITEABLE = 1;
10
  public const WPDEBUG_NOT_FOUND = 2;
11
- public const UNKNOWN_WPCONFIG = 3;
12
- public const IS_EMPTY = 4;
13
- public const VALIDATE = 5;
14
- public const SQL_ERROR = 6;
15
- public const DB_ERROR = 7;
16
- public const INVALID = 8;
17
- public const SCAN_ERROR = 9;
18
- public const API_ERROR = 10;
 
19
  }
6
  namespace WP_Defender\Component;
7
 
8
  class Error_Code {
9
+ public const NOT_WRITEABLE = 1;
10
  public const WPDEBUG_NOT_FOUND = 2;
11
+ public const UNKNOWN_WPCONFIG = 3;
12
+ public const IS_EMPTY = 4;
13
+ public const VALIDATE = 5;
14
+ public const SQL_ERROR = 6;
15
+ public const DB_ERROR = 7;
16
+ public const INVALID = 8;
17
+ public const SCAN_ERROR = 9;
18
+ public const API_ERROR = 10;
19
+ public const TFA_DECRYPT_ERROR = 11;
20
  }
src/component/legacy-versions.php CHANGED
@@ -334,4 +334,40 @@ class Legacy_Versions extends Component {
334
  public function change_onboarding_status() {
335
  update_site_option( 'wp_defender_shown_activator', true );
336
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  }
334
  public function change_onboarding_status() {
335
  update_site_option( 'wp_defender_shown_activator', true );
336
  }
337
+
338
+ /**
339
+ * @param string $encrypt_data
340
+ * @param string $key
341
+ *
342
+ * @return bool|string
343
+ */
344
+ private static function decrypt_data_with_pub_key( $encrypt_data, $key ) {
345
+ $str = base64_decode( $encrypt_data );
346
+ if ( ! $str ) {
347
+ return false;
348
+ }
349
+ $cipher = 'aes-256-cbc';
350
+ $iv_len = openssl_cipher_iv_length( $cipher );
351
+ $iv = substr( $str, 0, $iv_len );
352
+ $ciphertext_raw = substr( $str, $iv_len + 32 );
353
+
354
+ return openssl_decrypt( $ciphertext_raw, $cipher, $key, OPENSSL_RAW_DATA, $iv );
355
+ }
356
+
357
+ /**
358
+ * Backward compatible for decryption with pub key file.
359
+ *
360
+ * @param string $data
361
+ *
362
+ * @return bool|string
363
+ */
364
+ public static function get_decrypted_data_with_pub_key( $data ) {
365
+ $path_to_pub_key = __DIR__ . '/def.key';
366
+ if ( ! file_exists( $path_to_pub_key ) ) {
367
+ return false;
368
+ }
369
+ $key = file_get_contents( $path_to_pub_key );
370
+
371
+ return false !== $key ? self::decrypt_data_with_pub_key( $data, $key ) : '';
372
+ }
373
  }
src/component/two-fa.php CHANGED
@@ -4,6 +4,7 @@ namespace WP_Defender\Component;
4
 
5
  use Calotes\Base\Component;
6
  use Calotes\Helper\Array_Cache;
 
7
  use WP_Defender\Component\Crypt;
8
  use WP_Defender\Model\Setting\Two_Fa as Two_Fa_Model;
9
  use WP_Defender\Component\Two_Factor\Providers\Totp;
@@ -119,15 +120,18 @@ class Two_Fa extends Component {
119
  * @param string $user_code
120
  * @param WP_User|null $user
121
  *
122
- * @return bool
123
  */
124
- public function verify_otp( string $user_code, $user = null ): bool {
125
  if ( strlen( $user_code ) < Totp::TOTP_DIGIT_COUNT ) {
126
  return false;
127
  }
128
  for ( $i = - 30; $i <= Totp::TOTP_TIME_STEP_SEC; $i ++ ) {
129
  $counter = 0 === $i ? null : $i * Totp::TOTP_TIME_STEP_SEC + time();
130
  $code = Totp::generate_otp( $counter, $user );
 
 
 
131
  if ( Crypt::compare_lines( $user_code, $code ) ) {
132
  return true;
133
  }
@@ -568,6 +572,8 @@ class Two_Fa extends Component {
568
  // If the time difference between attempts is greater than the limit, clear the attempt counter.
569
  if ( $end_time - $start_time >= $time_limit ) {
570
  delete_user_meta( $user_id, 'wd_2fa_attempt_' . $slug );
 
 
571
  }
572
 
573
  $count = (int) $count -1;
@@ -599,4 +605,52 @@ class Two_Fa extends Component {
599
 
600
  return $lockout_message;
601
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
602
  }
4
 
5
  use Calotes\Base\Component;
6
  use Calotes\Helper\Array_Cache;
7
+ use WP_Defender\Component\Legacy_Versions;
8
  use WP_Defender\Component\Crypt;
9
  use WP_Defender\Model\Setting\Two_Fa as Two_Fa_Model;
10
  use WP_Defender\Component\Two_Factor\Providers\Totp;
120
  * @param string $user_code
121
  * @param WP_User|null $user
122
  *
123
+ * @return bool|WP_Error
124
  */
125
+ public function verify_otp( string $user_code, $user = null ) {
126
  if ( strlen( $user_code ) < Totp::TOTP_DIGIT_COUNT ) {
127
  return false;
128
  }
129
  for ( $i = - 30; $i <= Totp::TOTP_TIME_STEP_SEC; $i ++ ) {
130
  $counter = 0 === $i ? null : $i * Totp::TOTP_TIME_STEP_SEC + time();
131
  $code = Totp::generate_otp( $counter, $user );
132
+ if ( is_wp_error( $code ) ) {
133
+ return $code;
134
+ }
135
  if ( Crypt::compare_lines( $user_code, $code ) ) {
136
  return true;
137
  }
572
  // If the time difference between attempts is greater than the limit, clear the attempt counter.
573
  if ( $end_time - $start_time >= $time_limit ) {
574
  delete_user_meta( $user_id, 'wd_2fa_attempt_' . $slug );
575
+ $count = $this->get_attempt_limit();
576
+ $start_time = $end_time;
577
  }
578
 
579
  $count = (int) $count -1;
605
 
606
  return $lockout_message;
607
  }
608
+
609
+ /**
610
+ * @param int $user_id User ID.
611
+ * @param string $plaintext Clear text.
612
+ * @param string $state Previous state.
613
+ *
614
+ * @return bool
615
+ */
616
+ protected function reencrypt_data( $user_id, $plaintext, $state = 'plaintext' ) {
617
+ $new_key = Crypt::get_encrypted_data( $plaintext );
618
+ if ( ! is_wp_error( $new_key ) ) {
619
+ // If everything is successful, remove an old key.
620
+ delete_user_meta( $user_id, TOTP::TOTP_SECRET_KEY );
621
+ // Update the value with a new key.
622
+ update_user_meta( $user_id, TOTP::TOTP_SODIUM_SECRET_KEY, $new_key );
623
+ // Logging.
624
+ $this->log( 'Update UID: ' . $user_id . '. Previous state: ' . $state, 'internal' );
625
+
626
+ return true;
627
+ }
628
+ $this->log( 'Encryption error for UID: ' . $user_id . '. State: ' . $state, 'internal' );
629
+
630
+ return false;
631
+ }
632
+
633
+ /**
634
+ * Old states with plaintext or pub key are exist?
635
+ *
636
+ * @param int $user_id
637
+ *
638
+ * @return bool
639
+ */
640
+ public function maybe_update( $user_id ) {
641
+ $old_key = get_user_meta( $user_id, Totp::TOTP_SECRET_KEY, true );
642
+ if ( ! empty( $old_key ) && is_string( $old_key ) ) {
643
+ // Is it a plaintext? It was before v3.3.1.
644
+ if ( TOTP::TOTP_LENGTH === mb_strlen( $old_key, '8bit' ) ) {
645
+ return $this->reencrypt_data( $user_id, $old_key );
646
+ }
647
+ // Is it encrypted via a pub key? It was before v3.4.0.
648
+ $decrypted_data = Legacy_Versions::get_decrypted_data_with_pub_key( $old_key );
649
+ if ( false !== $decrypted_data ) {
650
+ return $this->reencrypt_data( $user_id, $decrypted_data, 'pub-key' );
651
+ }
652
+ }
653
+
654
+ return false;
655
+ }
656
  }
src/component/two-factor/providers/totp.php CHANGED
@@ -4,6 +4,7 @@ declare( strict_types = 1 );
4
  namespace WP_Defender\Component\Two_Factor\Providers;
5
 
6
  use Calotes\Helper\HTTP;
 
7
  use WP_Defender\Component\Crypt;
8
  use WP_Defender\Component\Two_Factor\Two_Factor_Provider;
9
  use WP_Defender\Extra\Base2n;
@@ -34,10 +35,19 @@ class Totp extends Two_Factor_Provider {
34
  public const TOTP_AUTH_KEY = 'defenderAuthOn';
35
 
36
  /**
 
 
37
  * @type string
38
  */
39
  public const TOTP_SECRET_KEY = 'defenderAuthSecret';
40
 
 
 
 
 
 
 
 
41
  /**
42
  * @type string
43
  */
@@ -168,6 +178,20 @@ class Totp extends Two_Factor_Provider {
168
  ]
169
  );
170
  } else {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  $this->get_controller()->render_partial(
172
  'two-fa/providers/totp-disabled',
173
  [
@@ -175,8 +199,9 @@ class Totp extends Two_Factor_Provider {
175
  'default_message' => $default_values['message'],
176
  'auth_apps' => $this->get_auth_apps(),
177
  'user' => $user,
178
- 'secret_key' => self::get_user_secret(),
179
  'class' => $service->is_checked_enabled_provider_by_slug( $user, self::$slug ) ? '' : 'hidden',
 
180
  ]
181
  );
182
  }
@@ -232,24 +257,36 @@ class Totp extends Two_Factor_Provider {
232
  /**
233
  * @param WP_User|null $user
234
  *
235
- * @return mixed|string
236
  */
237
- public static function get_user_secret( $user = null ) {
238
  // This should only use in testing.
239
  if ( is_object( $user ) ) {
240
  $user_id = $user->ID;
241
  } else {
242
  $user_id = get_current_user_id();
243
  }
244
-
245
- $data = get_user_meta( $user_id, self::TOTP_SECRET_KEY, true );
246
  if ( ! empty( $data ) ) {
247
- return Crypt::get_decrypted_string( $data );
 
 
 
 
 
 
 
 
248
  }
249
- // No data then add new one.
250
  $plaintext = defender_generate_random_string( self::TOTP_LENGTH, self::TOTP_CHARACTERS );
251
- $secret = Crypt::get_encrypted_string( $plaintext );
252
- update_user_meta( $user_id, self::TOTP_SECRET_KEY, $secret );
 
 
 
 
253
 
254
  return $plaintext;
255
  }
@@ -260,12 +297,16 @@ class Totp extends Two_Factor_Provider {
260
  * @param int|null $counter
261
  * @param WP_User|null $user
262
  *
263
- * @return string
264
  */
265
  public static function generate_otp( $counter = null, $user = null ) {
 
 
 
 
266
  include_once defender_path( 'src/extra/binary-to-text-php/Base2n.php' );
267
  $base32 = new Base2n( 5, self::TOTP_CHARACTERS, false, true, true );
268
- $secret = $base32->decode( self::get_user_secret( $user ) );
269
  if ( is_null( $counter ) ) {
270
  $counter = time();
271
  }
4
  namespace WP_Defender\Component\Two_Factor\Providers;
5
 
6
  use Calotes\Helper\HTTP;
7
+ use WP_Defender\Component\Two_Fa as Two_Fa_Component;
8
  use WP_Defender\Component\Crypt;
9
  use WP_Defender\Component\Two_Factor\Two_Factor_Provider;
10
  use WP_Defender\Extra\Base2n;
35
  public const TOTP_AUTH_KEY = 'defenderAuthOn';
36
 
37
  /**
38
+ * Used def.key before v3.4.0.
39
+ *
40
  * @type string
41
  */
42
  public const TOTP_SECRET_KEY = 'defenderAuthSecret';
43
 
44
+ /**
45
+ * Use Sodium library since v3.4.0.
46
+ *
47
+ * @type string
48
+ */
49
+ public const TOTP_SODIUM_SECRET_KEY = 'defenderAuthSodiumSecret';
50
+
51
  /**
52
  * @type string
53
  */
178
  ]
179
  );
180
  } else {
181
+ $is_success = true;
182
+ $result = self::get_user_secret();
183
+ if ( is_wp_error( $result ) ) {
184
+ $secret = $result->get_error_message();
185
+ $is_success = false;
186
+ } elseif ( is_bool( $result ) ) {
187
+ // Sometimes we can get a boolean value due to errors with writing to the database. In this case, we need to reset the value.
188
+ delete_user_meta( $user->ID, self::TOTP_SECRET_KEY );
189
+ // Also for new key.
190
+ delete_user_meta( $user->ID, self::TOTP_SODIUM_SECRET_KEY );
191
+ $secret = self::get_user_secret();
192
+ } else {
193
+ $secret = $result;
194
+ }
195
  $this->get_controller()->render_partial(
196
  'two-fa/providers/totp-disabled',
197
  [
199
  'default_message' => $default_values['message'],
200
  'auth_apps' => $this->get_auth_apps(),
201
  'user' => $user,
202
+ 'secret_key' => $secret,
203
  'class' => $service->is_checked_enabled_provider_by_slug( $user, self::$slug ) ? '' : 'hidden',
204
+ 'is_success' => $is_success,
205
  ]
206
  );
207
  }
257
  /**
258
  * @param WP_User|null $user
259
  *
260
+ * @return string|WP_Error|bool
261
  */
262
+ private static function get_user_secret( $user = null ) {
263
  // This should only use in testing.
264
  if ( is_object( $user ) ) {
265
  $user_id = $user->ID;
266
  } else {
267
  $user_id = get_current_user_id();
268
  }
269
+ // First, we check the new 'TOTP_SODIUM_SECRET_KEY' key.
270
+ $data = get_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, true );
271
  if ( ! empty( $data ) ) {
272
+ return Crypt::get_decrypted_data( $data );
273
+ }
274
+ // Then check the old 'TOTP_SECRET_KEY' key.
275
+ if ( ( new Two_Fa_Component() )->maybe_update( $user_id ) ) {
276
+ // Check a new key again.
277
+ $data = get_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, true );
278
+ if ( ! empty( $data ) && is_string( $data ) ) {
279
+ return Crypt::get_decrypted_data( $data );
280
+ }
281
  }
282
+ // Finally, add a new one.
283
  $plaintext = defender_generate_random_string( self::TOTP_LENGTH, self::TOTP_CHARACTERS );
284
+ $secret = Crypt::get_encrypted_data( $plaintext );
285
+ if ( is_wp_error( $secret ) ) {
286
+ return $secret;
287
+ }
288
+ // Todo: Maybe save token only when we verify it?
289
+ update_user_meta( $user_id, self::TOTP_SODIUM_SECRET_KEY, $secret );
290
 
291
  return $plaintext;
292
  }
297
  * @param int|null $counter
298
  * @param WP_User|null $user
299
  *
300
+ * @return string|WP_Error
301
  */
302
  public static function generate_otp( $counter = null, $user = null ) {
303
+ $result = self::get_user_secret( $user );
304
+ if ( is_wp_error( $result ) ) {
305
+ return $result;
306
+ }
307
  include_once defender_path( 'src/extra/binary-to-text-php/Base2n.php' );
308
  $base32 = new Base2n( 5, self::TOTP_CHARACTERS, false, true, true );
309
+ $secret = $base32->decode( $result );
310
  if ( is_null( $counter ) ) {
311
  $counter = time();
312
  }
src/component/wp-defender-sample.php ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // The file is used to generate and validate secret keys. Deleting this file or manually editing the content will permanently break 2fa TOTP method for all users.
3
+ if ( ! defined( 'WPINC' ) ) {
4
+ die;
5
+ }
6
+ // WP Defender - Start
7
+ define( 'WP_DEFENDER_TOTP_KEY', '{{__REPLACE_CODE__}}' );
8
+ // WP Defender - End
src/controller/two-factor.php CHANGED
@@ -477,7 +477,7 @@ class Two_Factor extends Controller {
477
  $params['error'] = null;
478
  }
479
  // If this goes here then the current user is ok, need to show the 2 auth.
480
- $this->attach_behavior( 'wpmudev', WPMUDEV::class );
481
  $custom_graphic = '';
482
  $custom_graphic_type = '';
483
  $settings = new Two_Fa();
@@ -598,8 +598,10 @@ class Two_Factor extends Controller {
598
  public function disable_totp() {
599
  $user_id = get_current_user_id();
600
  update_user_meta( $user_id, Totp::TOTP_AUTH_KEY, 0 );
601
- // Remove secret key.
602
  delete_user_meta( $user_id, Totp::TOTP_SECRET_KEY );
 
 
603
  // Remove TOTP from enabled providers.
604
  $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
605
  if ( isset( $enabled_providers ) && ! empty( $enabled_providers ) ) {
@@ -613,6 +615,11 @@ class Two_Factor extends Controller {
613
  $enabled_providers = '';
614
  }
615
  update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
 
 
 
 
 
616
 
617
  return new Response( true, [] );
618
  }
@@ -639,7 +646,15 @@ class Two_Factor extends Controller {
639
  );
640
  }
641
  //Todo: maybe new validate_code() method from TOTP provider class.
642
- if ( $this->service->verify_otp( $otp ) ) {
 
 
 
 
 
 
 
 
643
  $user_id = get_current_user_id();
644
  $this->service->enable_otp( $user_id );
645
  $totp_slug = Totp::$slug;
@@ -1030,8 +1045,9 @@ class Two_Factor extends Controller {
1030
  // From Totp.
1031
  'wd_2fa_attempt_' . TOTP::$slug,
1032
  TOTP::TOTP_AUTH_KEY,
1033
- // We keep it in file system, but we will remove this key in future versions, e.g. 3.4.0.
1034
  TOTP::TOTP_SECRET_KEY,
 
1035
  TOTP::TOTP_FORCE_KEY,
1036
  // From Backup_Codes.
1037
  'wd_2fa_attempt_' . Backup_Codes::$slug,
@@ -1053,6 +1069,11 @@ class Two_Factor extends Controller {
1053
  // Delete 2fa file. It's actual for prev v3.3.1.
1054
  @unlink( $file );
1055
  }
 
 
 
 
 
1056
  }
1057
 
1058
  /**
477
  $params['error'] = null;
478
  }
479
  // If this goes here then the current user is ok, need to show the 2 auth.
480
+ $this->attach_behavior( WPMUDEV::class, WPMUDEV::class );
481
  $custom_graphic = '';
482
  $custom_graphic_type = '';
483
  $settings = new Two_Fa();
598
  public function disable_totp() {
599
  $user_id = get_current_user_id();
600
  update_user_meta( $user_id, Totp::TOTP_AUTH_KEY, 0 );
601
+ // Remove old secret key.
602
  delete_user_meta( $user_id, Totp::TOTP_SECRET_KEY );
603
+ // Remove new secret key.
604
+ delete_user_meta( $user_id, Totp::TOTP_SODIUM_SECRET_KEY );
605
  // Remove TOTP from enabled providers.
606
  $enabled_providers = get_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, true );
607
  if ( isset( $enabled_providers ) && ! empty( $enabled_providers ) ) {
615
  $enabled_providers = '';
616
  }
617
  update_user_meta( $user_id, Two_Fa_Component::ENABLED_PROVIDERS_USER_KEY, $enabled_providers );
618
+ // Check the default provider. If it's TOTP then clear the value.
619
+ $default_provider = get_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, true );
620
+ if ( ! empty( $default_provider ) && $default_provider === Totp::$slug ) {
621
+ update_user_meta( $user_id, Two_Fa_Component::DEFAULT_PROVIDER_USER_KEY, '' );
622
+ }
623
 
624
  return new Response( true, [] );
625
  }
646
  );
647
  }
648
  //Todo: maybe new validate_code() method from TOTP provider class.
649
+ $result = $this->service->verify_otp( $otp );
650
+ // OTP result can be a boolean value or WP error.
651
+ if ( is_wp_error( $result ) ) {
652
+ return new Response(
653
+ false,
654
+ [ 'message' => $result->get_error_message() ]
655
+ );
656
+ }
657
+ if ( $result ) {
658
  $user_id = get_current_user_id();
659
  $this->service->enable_otp( $user_id );
660
  $totp_slug = Totp::$slug;
1045
  // From Totp.
1046
  'wd_2fa_attempt_' . TOTP::$slug,
1047
  TOTP::TOTP_AUTH_KEY,
1048
+ // For backward compatible with the def.key file. We'll remove this key in future versions and use the key for Sodium.
1049
  TOTP::TOTP_SECRET_KEY,
1050
+ TOTP::TOTP_SODIUM_SECRET_KEY,
1051
  TOTP::TOTP_FORCE_KEY,
1052
  // From Backup_Codes.
1053
  'wd_2fa_attempt_' . Backup_Codes::$slug,
1069
  // Delete 2fa file. It's actual for prev v3.3.1.
1070
  @unlink( $file );
1071
  }
1072
+ // Check if the file with a random key exists.
1073
+ $file = Crypt::get_path_to_key_file();
1074
+ if ( is_file( $file ) && is_readable( $file ) ) {
1075
+ @unlink( $file );
1076
+ }
1077
  }
1078
 
1079
  /**
src/upgrader.php CHANGED
@@ -3,6 +3,7 @@ declare( strict_types=1 );
3
 
4
  namespace WP_Defender;
5
 
 
6
  use WP_Defender\Component\Feature_Modal;
7
  use WP_Defender\Component\Two_Factor\Providers\Fallback_Email;
8
  use WP_Defender\Component\Two_Factor\Providers\Totp;
@@ -321,8 +322,8 @@ class Upgrader {
321
  if ( version_compare( $db_version, '3.3.1', '<' ) ) {
322
  $this->upgrade_3_3_1();
323
  }
324
- if ( version_compare( $db_version, '3.3.2', '<' ) ) {
325
- $this->upgrade_3_3_2();
326
  }
327
 
328
  defender_no_fresh_install();
@@ -1098,101 +1099,40 @@ Your temporary password is {{passcode}}. To finish logging in, copy and paste th
1098
  }
1099
 
1100
  /**
1101
- * @param string $file
1102
- * @param array $arr_user_ids
1103
  *
1104
- * @return array
 
 
1105
  */
1106
- private function convert_2fa_lines( $file, &$arr_user_ids ): array {
1107
- if ( is_file( $file ) && is_readable( $file ) ) {
1108
- $content = file_get_contents( $file );
1109
- if ( trim( $content ) ) {
1110
- $lines = preg_split( '/[\r\n]+/', $content );
1111
- if ( is_array( $lines ) && ! empty( $lines ) ) {
1112
- foreach ( $lines as $line ) {
1113
- if ( ! empty( trim( $line ) ) ) {
1114
- [ $user_id, $skey ] = explode( '://:', $line );
1115
- $user_id = (int) $user_id;
1116
- // User ID's can be repeated for MU. Repetition must be avoided.
1117
- if ( in_array( $user_id, $arr_user_ids, true ) ) {
1118
- continue;
1119
- }
1120
- $plaintext = get_user_meta( $user_id, Totp::TOTP_SECRET_KEY, true );
1121
- // Check that the plaintext of the key exists.
1122
- if ( ! empty( $plaintext ) ) {
1123
- // User_meta has an advantage over lock-file.
1124
- $secret = Crypt::get_encrypted_string( $plaintext );
1125
- } else {
1126
- $secret = Crypt::get_encrypted_string( $skey );
1127
- }
1128
- // Update value.
1129
- update_user_meta( $user_id, Totp::TOTP_SECRET_KEY, $secret );
1130
- $arr_user_ids[] = $user_id;
1131
  }
1132
  }
1133
  }
1134
  }
1135
- // Delete 2fa file.
1136
- @unlink( $file );
1137
- }
1138
-
1139
- return $arr_user_ids;
1140
- }
1141
-
1142
- /**
1143
- * @return array
1144
- */
1145
- private function encrypt_secret_keys(): array {
1146
- $arr_user_ids = [];
1147
- // It's for a single site or for the main site on MU.
1148
- $file = $this->get_2fa_lock_path();
1149
- $arr_user_ids = $this->convert_2fa_lines( $file, $arr_user_ids );
1150
- // For other subsites on MU.
1151
- if ( is_multisite() ) {
1152
- $site_ids = get_sites( [ 'fields' => 'ids', 'site__not_in' => get_main_site_id() ] );
1153
- if ( ! empty( $site_ids ) ) {
1154
- $upload_dir = wp_upload_dir()['basedir'];
1155
- foreach ( $site_ids as $site_id ) {
1156
- $file = $upload_dir . DIRECTORY_SEPARATOR . 'sites' . DIRECTORY_SEPARATOR . $site_id
1157
- . DIRECTORY_SEPARATOR . 'wp-defender' . DIRECTORY_SEPARATOR . 'two-fa.lock';
1158
- $arr_user_ids = $this->convert_2fa_lines( $file, $arr_user_ids );
1159
- }
1160
- }
1161
- }
1162
-
1163
- return $arr_user_ids;
1164
- }
1165
-
1166
- /**
1167
- * Upgrade to 3.3.2: update auth secret keys.
1168
- *
1169
- * @return void
1170
- */
1171
- private function upgrade_3_3_2() {
1172
- // Data from a 2FA file. It's after the previous v3.3.1.
1173
- $excluded_ids = $this->encrypt_secret_keys();
1174
- // Data from plugin versions where the 2FA file concept was not used, before v3.3.1.
1175
- $query = new \WP_User_Query(
1176
- [
1177
- 'blog_id' => 0,
1178
- 'meta_key' => Totp::TOTP_SECRET_KEY,
1179
- 'fields' => 'ID',
1180
- ]
1181
- );
1182
- if ( $query->get_total() > 0 ) {
1183
- foreach ( $query->get_results() as $user_id ) {
1184
- // Exclude users whose data we have already updated.
1185
- if ( in_array( $user_id, $excluded_ids, true ) ) {
1186
- continue;
1187
- }
1188
- $plaintext = get_user_meta( $user_id, Totp::TOTP_SECRET_KEY, true );
1189
- // Check that the plaintext is existed and no encrypted. Encrypted line has '=' symbol.
1190
- if ( ! empty( $plaintext ) && false === strpos( $plaintext, '=' ) ) {
1191
- $secret = Crypt::get_encrypted_string( $plaintext );
1192
- // Update value.
1193
- update_user_meta( $user_id, Totp::TOTP_SECRET_KEY, $secret );
1194
- }
1195
- }
1196
  }
1197
  }
1198
  }
3
 
4
  namespace WP_Defender;
5
 
6
+ use Safe\Exceptions\SodiumException;
7
  use WP_Defender\Component\Feature_Modal;
8
  use WP_Defender\Component\Two_Factor\Providers\Fallback_Email;
9
  use WP_Defender\Component\Two_Factor\Providers\Totp;
322
  if ( version_compare( $db_version, '3.3.1', '<' ) ) {
323
  $this->upgrade_3_3_1();
324
  }
325
+ if ( version_compare( $db_version, '3.3.3', '<' ) ) {
326
+ $this->upgrade_3_3_3();
327
  }
328
 
329
  defender_no_fresh_install();
1099
  }
1100
 
1101
  /**
1102
+ * Upgrade to 3.3.3: update 2FA flow for secret keys.
 
1103
  *
1104
+ * @since 3.3.3
1105
+ * @return void
1106
+ * @throws SodiumException
1107
  */
1108
+ private function upgrade_3_3_3(): void {
1109
+ // Create a file with a random key if it doesn't exist.
1110
+ if ( ( new Crypt() )->create_key_file() ) {
1111
+ // Get user data.
1112
+ $query = new \WP_User_Query(
1113
+ [
1114
+ 'blog_id' => 0,
1115
+ 'meta_key' => Totp::TOTP_SECRET_KEY,
1116
+ 'fields' => 'ID',
1117
+ ]
1118
+ );
1119
+ if ( $query->get_total() > 0 ) {
1120
+ $service = wd_di()->get( Two_Fa_Component::class );
1121
+ foreach ( $query->get_results() as $user_id ) {
1122
+ // If we have the old states (cleartext, pub key) then re-encrypt via Sodium and save it.
1123
+ if ( ! $service->maybe_update( $user_id ) ) {
1124
+ // Otherwise just encrypt it via Sodium.
1125
+ $plaintext = defender_generate_random_string( TOTP::TOTP_LENGTH, TOTP::TOTP_CHARACTERS );
1126
+ $new_key = Crypt::get_encrypted_data( $plaintext );
1127
+ if ( ! is_wp_error( $new_key ) ) {
1128
+ // Remove an old key.
1129
+ delete_user_meta( $user_id, TOTP::TOTP_SECRET_KEY );
1130
+ // Update a new one.
1131
+ update_user_meta( $user_id, TOTP::TOTP_SODIUM_SECRET_KEY, $new_key );
 
1132
  }
1133
  }
1134
  }
1135
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1136
  }
1137
  }
1138
  }
src/view/two-fa/providers/totp-disabled.php CHANGED
@@ -25,24 +25,28 @@
25
  <strong>
26
  <?php _e( '2. Scan the QR code or enter the key', 'wpdef' ); ?>
27
  </strong>
28
- <p class="wd_text_wrap">
 
 
 
 
 
 
 
 
29
  <?php
30
- _e(
31
- 'Open the authenticator app, and scan the QR code below or manually enter the setup key to add your new site.',
32
- 'wpdef'
33
- )
34
  ?>
35
- </p>
36
- <?php
37
- WP_Defender\Component\Two_Factor\Providers\Totp::generate_qr_code( $secret_key );
38
- ?>
39
- <p class="wd_code_wrap">
40
- <code id="wd_clipboard"><?php echo esc_html( $secret_key ); ?></code>
41
- <button type="button" class="button" id="wd_copy_2fa_key"
42
- data-clipborad-action="copy" data-clipboard-target="#wd_clipboard">
43
- <?php _e( 'Copy', 'wpdef' ); ?>
44
- </button>
45
- </p>
46
  <div class="line"></div>
47
  <strong>
48
  <?php _e( '3. Enter passcode', 'wpdef' ); ?>
@@ -59,7 +63,7 @@
59
  <p class="error"></p>
60
  <input type="text" id="otp-code" class="def-small-text"
61
  placeholder="<?php _e( 'Enter passcode', 'wpdef' ); ?>" />
62
- <button type="button" class="button button-primary" id="verify-otp">
63
  <?php _e( 'Verify', 'wpdef' ); ?>
64
  </button>
65
  </div>
25
  <strong>
26
  <?php _e( '2. Scan the QR code or enter the key', 'wpdef' ); ?>
27
  </strong>
28
+ <?php if ( $is_success ) { ?>
29
+ <p class="wd_text_wrap">
30
+ <?php
31
+ _e(
32
+ 'Open the authenticator app, and scan the QR code below or manually enter the setup key to add your new site.',
33
+ 'wpdef'
34
+ )
35
+ ?>
36
+ </p>
37
  <?php
38
+ WP_Defender\Component\Two_Factor\Providers\Totp::generate_qr_code( $secret_key );
 
 
 
39
  ?>
40
+ <p class="wd_code_wrap">
41
+ <code id="wd_clipboard"><?php echo esc_html( $secret_key ); ?></code>
42
+ <button type="button" class="button" id="wd_copy_2fa_key"
43
+ data-clipborad-action="copy" data-clipboard-target="#wd_clipboard">
44
+ <?php _e( 'Copy', 'wpdef' ); ?>
45
+ </button>
46
+ </p>
47
+ <?php } else { ?>
48
+ <p class="error_process"><?php echo esc_html( $secret_key ); ?></p>
49
+ <?php } ?>
 
50
  <div class="line"></div>
51
  <strong>
52
  <?php _e( '3. Enter passcode', 'wpdef' ); ?>
63
  <p class="error"></p>
64
  <input type="text" id="otp-code" class="def-small-text"
65
  placeholder="<?php _e( 'Enter passcode', 'wpdef' ); ?>" />
66
+ <button type="button" class="button button-primary" <?php echo $is_success ? 'id="verify-otp"' : 'disabled'; ?>>
67
  <?php _e( 'Verify', 'wpdef' ); ?>
68
  </button>
69
  </div>
wp-defender.php CHANGED
@@ -2,7 +2,7 @@
2
  /**
3
  * Plugin Name: Defender
4
  * Plugin URI: https://wpmudev.com/project/wp-defender/
5
- * Version: 3.3.2
6
  * Description: Get regular security scans, vulnerability reports, safety recommendations and customized hardening for your site in just a few clicks. Defender is the analyst and enforcer who never sleeps.
7
  * Author: WPMU DEV
8
  * Author URI: https://wpmudev.com/
@@ -33,10 +33,10 @@ if ( ! defined( 'ABSPATH' ) ) {
33
  die;
34
  }
35
  if ( ! defined( 'DEFENDER_VERSION' ) ) {
36
- define( 'DEFENDER_VERSION', '3.3.2' );
37
  }
38
  if ( ! defined( 'DEFENDER_DB_VERSION' ) ) {
39
- define( 'DEFENDER_DB_VERSION', '3.3.2' );
40
  }
41
  if ( ! defined( 'DEFENDER_SUI' ) ) {
42
  define( 'DEFENDER_SUI', '2-12-8' );
2
  /**
3
  * Plugin Name: Defender
4
  * Plugin URI: https://wpmudev.com/project/wp-defender/
5
+ * Version: 3.3.3
6
  * Description: Get regular security scans, vulnerability reports, safety recommendations and customized hardening for your site in just a few clicks. Defender is the analyst and enforcer who never sleeps.
7
  * Author: WPMU DEV
8
  * Author URI: https://wpmudev.com/
33
  die;
34
  }
35
  if ( ! defined( 'DEFENDER_VERSION' ) ) {
36
+ define( 'DEFENDER_VERSION', '3.3.3' );
37
  }
38
  if ( ! defined( 'DEFENDER_DB_VERSION' ) ) {
39
+ define( 'DEFENDER_DB_VERSION', '3.3.3' );
40
  }
41
  if ( ! defined( 'DEFENDER_SUI' ) ) {
42
  define( 'DEFENDER_SUI', '2-12-8' );