This looks like a new restriction. Could it introduce a breakage for users that have -rpcpassword=contains:colon
?
It is sub-optimal to join/craft the string “user:pass” and to split/parse it a few lines later. Better store the user and pass in separate variables, like this:
0diff --git i/src/httprpc.cpp w/src/httprpc.cpp
1index ed5a0253c6..493f1b0320 100644
2--- i/src/httprpc.cpp
3+++ w/src/httprpc.cpp
4@@ -275,13 +275,14 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req)
5 }
6 return true;
7 }
8
9 static bool InitRPCAuthentication()
10 {
11- std::string user_colon_pass;
12+ std::string user;
13+ std::string pass;
14
15 if (gArgs.GetArg("-rpcpassword", "") == "")
16 {
17 std::optional<fs::perms> cookie_perms{std::nullopt};
18 auto cookie_perms_arg{gArgs.GetArg("-rpccookieperms")};
19 if (cookie_perms_arg) {
20@@ -290,36 +291,31 @@ static bool InitRPCAuthentication()
21 LogError("Invalid -rpccookieperms=%s; must be one of 'owner', 'group', or 'all'.", *cookie_perms_arg);
22 return false;
23 }
24 cookie_perms = *perm_opt;
25 }
26
27- if (!GenerateAuthCookie(&user_colon_pass, cookie_perms)) {
28+ switch (GenerateAuthCookie(cookie_perms, user, pass)) {
29+ case GenerateAuthCookieResult::ERROR:
30 return false;
31- }
32- if (user_colon_pass.empty()) {
33+ case GenerateAuthCookieResult::DISABLED:
34 LogInfo("RPC authentication cookie file generation is disabled.");
35- } else {
36+ break;
37+ case GenerateAuthCookieResult::OK:
38 LogInfo("Using random cookie authentication.");
39+ break;
40 }
41 } else {
42 LogInfo("Using rpcuser/rpcpassword authentication.");
43 LogWarning("The use of rpcuser/rpcpassword is less secure, because credentials are configured in plain text. It is recommended that locally-run instances switch to cookie-based auth, or otherwise to use hashed rpcauth credentials. See share/rpcauth for more information.");
44- user_colon_pass = gArgs.GetArg("-rpcuser", "") + ":" + gArgs.GetArg("-rpcpassword", "");
45+ user = gArgs.GetArg("-rpcuser", "");
46+ pass = gArgs.GetArg("-rpcpassword", "");
47 }
48
49 // If there is a plaintext credential, hash it with a random salt before storage.
50- if (!user_colon_pass.empty()) {
51- std::vector<std::string> fields{SplitString(user_colon_pass, ':')};
52- if (fields.size() != 2) {
53- LogError("Unable to parse RPC credentials. The configured rpcuser or rpcpassword cannot contain a \":\".");
54- return false;
55- }
56- const std::string& user = fields[0];
57- const std::string& pass = fields[1];
58-
59+ if (!user.empty() || !pass.empty()) {
60 // Generate a random 16 byte hex salt.
61 std::array<unsigned char, 16> raw_salt;
62 GetStrongRandBytes(raw_salt);
63 std::string salt = HexStr(raw_salt);
64
65 // Compute HMAC.
66diff --git i/src/rpc/request.cpp w/src/rpc/request.cpp
67index 30f4a4f51b..249c6cb621 100644
68--- i/src/rpc/request.cpp
69+++ w/src/rpc/request.cpp
70@@ -94,56 +94,59 @@ static fs::path GetAuthCookieFile(bool temp=false)
71 }
72 return AbsPathForConfigVal(gArgs, arg);
73 }
74
75 static bool g_generated_cookie = false;
76
77-bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms)
78+GenerateAuthCookieResult GenerateAuthCookie(const std::optional<fs::perms>& cookie_perms,
79+ std::string& user,
80+ std::string& pass)
81 {
82 const size_t COOKIE_SIZE = 32;
83 unsigned char rand_pwd[COOKIE_SIZE];
84 GetRandBytes(rand_pwd);
85- std::string cookie = COOKIEAUTH_USER + ":" + HexStr(rand_pwd);
86+ const std::string rand_pwd_hex{HexStr(rand_pwd)};
87+ std::string cookie = COOKIEAUTH_USER + ":" + rand_pwd_hex;
88
89 /** the umask determines what permissions are used to create this file -
90 * these are set to 0077 in common/system.cpp.
91 */
92 std::ofstream file;
93 fs::path filepath_tmp = GetAuthCookieFile(true);
94 if (filepath_tmp.empty()) {
95- return true; // -norpccookiefile
96+ return GenerateAuthCookieResult::DISABLED; // -norpccookiefile
97 }
98 file.open(filepath_tmp);
99 if (!file.is_open()) {
100 LogWarning("Unable to open cookie authentication file %s for writing", fs::PathToString(filepath_tmp));
101- return false;
102+ return GenerateAuthCookieResult::ERROR;
103 }
104 file << cookie;
105 file.close();
106
107 fs::path filepath = GetAuthCookieFile(false);
108 if (!RenameOver(filepath_tmp, filepath)) {
109 LogWarning("Unable to rename cookie authentication file %s to %s", fs::PathToString(filepath_tmp), fs::PathToString(filepath));
110- return false;
111+ return GenerateAuthCookieResult::ERROR;
112 }
113 if (cookie_perms) {
114 std::error_code code;
115 fs::permissions(filepath, cookie_perms.value(), fs::perm_options::replace, code);
116 if (code) {
117 LogWarning("Unable to set permissions on cookie authentication file %s", fs::PathToString(filepath));
118- return false;
119+ return GenerateAuthCookieResult::ERROR;
120 }
121 }
122
123 g_generated_cookie = true;
124 LogInfo("Generated RPC authentication cookie %s\n", fs::PathToString(filepath));
125 LogInfo("Permissions used for cookie: %s\n", PermsToSymbolicString(fs::status(filepath).permissions()));
126
127- if (cookie_out)
128- *cookie_out = cookie;
129- return true;
130+ user = COOKIEAUTH_USER;
131+ pass = rand_pwd_hex;
132+ return GenerateAuthCookieResult::OK;
133 }
134
135 bool GetAuthCookie(std::string *cookie_out)
136 {
137 std::ifstream file;
138 std::string cookie;
139diff --git i/src/rpc/request.h w/src/rpc/request.h
140index 24887e8691..e93df02ba6 100644
141--- i/src/rpc/request.h
142+++ w/src/rpc/request.h
143@@ -20,14 +20,31 @@ enum class JSONRPCVersion {
144
145 /** JSON-RPC 2.0 request, only used in bitcoin-cli **/
146 UniValue JSONRPCRequestObj(const std::string& strMethod, const UniValue& params, const UniValue& id);
147 UniValue JSONRPCReplyObj(UniValue result, UniValue error, std::optional<UniValue> id, JSONRPCVersion jsonrpc_version);
148 UniValue JSONRPCError(int code, const std::string& message);
149
150-/** Generate a new RPC authentication cookie and write it to disk */
151-bool GenerateAuthCookie(std::string* cookie_out, std::optional<fs::perms> cookie_perms=std::nullopt);
152+enum class GenerateAuthCookieResult : uint8_t {
153+ DISABLED, // -norpccookiefile
154+ ERROR,
155+ OK,
156+};
157+
158+/**
159+ * Generate a new RPC authentication cookie and write it to disk
160+ * [@param](/bitcoin-bitcoin/contributor/param/)[in] cookie_perms Filesystem permissions to use for the cookie file.
161+ * [@param](/bitcoin-bitcoin/contributor/param/)[out] user Generated username, only set if `OK` is returned.
162+ * [@param](/bitcoin-bitcoin/contributor/param/)[out] pass Generated password, only set if `OK` is returned.
163+ * [@retval](/bitcoin-bitcoin/contributor/retval/) GenerateAuthCookieResult::DISABLED Authentication via cookie is disabled.
164+ * [@retval](/bitcoin-bitcoin/contributor/retval/) GenerateAuthCookieResult::ERROR Error occurred, auth data could not be saved to disk.
165+ * [@retval](/bitcoin-bitcoin/contributor/retval/) GenerateAuthCookieResult::OK Auth data was generated, saved to disk and in `user` and `pass`.
166+ */
167+GenerateAuthCookieResult GenerateAuthCookie(const std::optional<fs::perms>& cookie_perms,
168+ std::string& user,
169+ std::string& pass);
170+
171 /** Read the RPC authentication cookie from disk */
172 bool GetAuthCookie(std::string *cookie_out);
173 /** Delete RPC authentication cookie from disk */
174 void DeleteAuthCookie();
175 /** Parse JSON-RPC batch reply into a vector */
176 std::vector<UniValue> JSONRPCProcessBatchReply(const UniValue& in);