The Battle for Wesnoth  1.15.1+dev
credentials.cpp
Go to the documentation of this file.
1 /*
2 Copyright (C) 2017-2018 by the Battle for Wesnoth Project https://www.wesnoth.org/
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY.
10 
11 See the COPYING file for more details.
12 */
13 
14 #include "credentials.hpp"
15 
16 #include "preferences/general.hpp"
18 #include "filesystem.hpp"
19 #include "log.hpp"
21 
22 #include <boost/algorithm/string.hpp>
23 
24 #include <algorithm>
25 #include <memory>
26 
27 #ifndef __APPLE__
28 #include <openssl/rc4.h>
29 #else
30 #include <CommonCrypto/CommonCryptor.h>
31 #endif
32 
33 #ifdef _WIN32
34 #include <boost/range/iterator_range.hpp>
35 #include <windows.h>
36 #endif
37 
38 static lg::log_domain log_config("config");
39 #define ERR_CFG LOG_STREAM(err , log_config)
40 
41 class secure_buffer : public std::vector<unsigned char>
42 {
43 public:
44  template<typename... T> secure_buffer(T&&... args)
45  : vector<unsigned char>(std::forward<T>(args)...)
46  {}
48  {
49  std::fill(begin(), end(), '\0');
50  }
51 };
52 
53 struct login_info
54 {
55  std::string username, server;
57  login_info(const std::string& username, const std::string& server, const secure_buffer& key)
58  : username(username), server(server), key(key)
59  {}
60  login_info(const std::string& username, const std::string& server)
61  : username(username), server(server), key()
62  {}
63  std::size_t size() const
64  {
65  return 3 + username.size() + server.size() + key.size();
66  }
67 };
68 
69 static std::vector<login_info> credentials;
70 
71 // Separate password entries with formfeed
72 static const unsigned char CREDENTIAL_SEPARATOR = '\f';
73 
74 static secure_buffer encrypt(const secure_buffer& text, const secure_buffer& key);
75 static secure_buffer decrypt(const secure_buffer& text, const secure_buffer& key);
76 static secure_buffer build_key(const std::string& server, const std::string& login);
77 static secure_buffer escape(const secure_buffer& text);
78 static secure_buffer unescape(const secure_buffer& text);
79 
80 static std::string get_system_username()
81 {
82  std::string res;
83 #ifdef _WIN32
84  wchar_t buffer[300];
85  DWORD size = 300;
86  if(GetUserNameW(buffer, &size)) {
87  //size includes a terminating null character.
88  assert(size > 0);
89  res = unicode_cast<std::string>(boost::iterator_range<wchar_t*>(buffer, buffer + size - 1));
90  }
91 #else
92  if(char* const login = getenv("USER")) {
93  res = login;
94  }
95 #endif
96  return res;
97 }
98 
99 static void clear_credentials()
100 {
101  // Zero them before clearing.
102  // Probably overly paranoid, but doesn't hurt?
103  for(auto& cred : credentials) {
104  std::fill(cred.username.begin(), cred.username.end(), '\0');
105  std::fill(cred.server.begin(), cred.server.end(), '\0');
106  }
107  credentials.clear();
108 }
109 
110 static const std::string EMPTY_LOGIN = "@@";
111 
112 namespace preferences
113 {
114  std::string login()
115  {
116  std::string name = preferences::get("login", EMPTY_LOGIN);
117  if(name == EMPTY_LOGIN) {
118  name = get_system_username();
119  } else if(name.size() > 2 && name.front() == '@' && name.back() == '@') {
120  name = name.substr(1, name.size() - 2);
121  } else {
122  ERR_CFG << "malformed user credentials (did you manually edit the preferences file?)" << std::endl;
123  }
124  if(name.empty()) {
125  return "player";
126  }
127  return name;
128  }
129 
130  void set_login(const std::string& login)
131  {
132  auto login_clean = login;
133  boost::trim(login_clean);
134 
135  preferences::set("login", '@' + login_clean + '@');
136  }
137 
139  {
140  return preferences::get("remember_password", false);
141  }
142 
143  void set_remember_password(bool remember)
144  {
145  preferences::set("remember_password", remember);
146 
147  if(remember) {
149  } else {
151  }
152  }
153 
154  std::string password(const std::string& server, const std::string& login)
155  {
156  auto login_clean = login;
157  boost::trim(login_clean);
158 
159  if(!remember_password()) {
160  if(!credentials.empty() && credentials[0].username == login_clean && credentials[0].server == server) {
161  auto temp = decrypt(credentials[0].key, build_key(server, login_clean));
162  return std::string(temp.begin(), temp.end());
163  } else {
164  return "";
165  }
166  }
167  auto cred = std::find_if(credentials.begin(), credentials.end(), [&](const login_info& cred) {
168  return cred.server == server && cred.username == login_clean;
169  });
170  if(cred == credentials.end()) {
171  return "";
172  }
173  auto temp = decrypt(cred->key, build_key(server, login_clean));
174  return std::string(temp.begin(), temp.end());
175  }
176 
177  void set_password(const std::string& server, const std::string& login, const std::string& key)
178  {
179  auto login_clean = login;
180  boost::trim(login_clean);
181 
182  secure_buffer temp(key.begin(), key.end());
183  if(!remember_password()) {
185  credentials.emplace_back(login_clean, server, encrypt(temp, build_key(server, login_clean)));
186  return;
187  }
188  auto cred = std::find_if(credentials.begin(), credentials.end(), [&](const login_info& cred) {
189  return cred.server == server && cred.username == login_clean;
190  });
191  if(cred == credentials.end()) {
192  // This is equivalent to emplace_back, but also returns the iterator to the new element
193  cred = credentials.emplace(credentials.end(), login_clean, server);
194  }
195  cred->key = encrypt(temp, build_key(server, login_clean));
196  }
197 
199  {
200  if(!remember_password()) {
201  return;
202  }
204  std::string cred_file = filesystem::get_credentials_file();
205  if(!filesystem::file_exists(cred_file)) {
206  return;
207  }
208  filesystem::scoped_istream stream = filesystem::istream_file(cred_file, false);
209  // Credentials file is a binary blob, so use streambuf iterator
210  secure_buffer data((std::istreambuf_iterator<char>(*stream)), (std::istreambuf_iterator<char>()));
211  data = decrypt(data, build_key("global", get_system_username()));
212  if(data.empty() || data[0] != CREDENTIAL_SEPARATOR) {
213  ERR_CFG << "Invalid data in credentials file\n";
214  return;
215  }
216  for(const std::string& elem : utils::split(std::string(data.begin(), data.end()), CREDENTIAL_SEPARATOR, utils::REMOVE_EMPTY)) {
217  std::size_t at = elem.find_last_of('@');
218  std::size_t eq = elem.find_first_of('=', at + 1);
219  if(at != std::string::npos && eq != std::string::npos) {
220  secure_buffer key(elem.begin() + eq + 1, elem.end());
221  credentials.emplace_back(elem.substr(0, at), elem.substr(at + 1, eq - at - 1), unescape(key));
222  }
223  }
224  }
225 
227  {
228  if(!remember_password()) {
230  return;
231  }
232  secure_buffer credentials_data(1, CREDENTIAL_SEPARATOR);
233  std::size_t offset = 1;
234  for(const auto& cred : credentials) {
235  credentials_data.resize(credentials_data.size() + cred.size(), CREDENTIAL_SEPARATOR);
236  std::copy(cred.username.begin(), cred.username.end(), credentials_data.begin() + offset);
237  offset += cred.username.size();
238  credentials_data[offset++] = '@';
239  std::copy(cred.server.begin(), cred.server.end(), credentials_data.begin() + offset);
240  offset += cred.server.size();
241  credentials_data[offset++] = '=';
242  secure_buffer key_escaped = escape(cred.key);
243  // Escaping may increase the length, so resize again if so
244  credentials_data.resize(credentials_data.size() + key_escaped.size() - cred.key.size());
245  std::copy(key_escaped.begin(), key_escaped.end(), credentials_data.begin() + offset);
246  offset += key_escaped.size() + 1;
247  }
248  try {
250  secure_buffer encrypted = encrypt(credentials_data, build_key("global", get_system_username()));
251  credentials_file->write(reinterpret_cast<const char*>(encrypted.data()), encrypted.size());
252  } catch(const filesystem::io_exception&) {
253  ERR_CFG << "error writing to credentials file '" << filesystem::get_credentials_file() << "'" << std::endl;
254  }
255  }
256 }
257 
258 // TODO: Key-stretching (bcrypt was recommended)
259 secure_buffer build_key(const std::string& server, const std::string& login)
260 {
261  std::string sysname = get_system_username();
262  secure_buffer result(std::max<std::size_t>(server.size() + login.size() + sysname.size(), 32));
263  unsigned char i = 0;
264  std::generate(result.begin(), result.end(), [&i]() {return 'x' ^ i++;});
265  std::copy(login.begin(), login.end(), result.begin());
266  std::copy(sysname.begin(), sysname.end(), result.begin() + login.size());
267  std::copy(server.begin(), server.end(), result.begin() + login.size() + sysname.size());
268  return result;
269 }
270 
271 static secure_buffer rc4_crypt(const secure_buffer& text, const secure_buffer& key)
272 {
273  secure_buffer result(text.size(), '\0');
274 #ifndef __APPLE__
275  RC4_KEY cipher_key;
276  RC4_set_key(&cipher_key, key.size(), key.data());
277  const std::size_t block_size = key.size();
278  const std::size_t blocks = text.size() / block_size;
279  const std::size_t extra = text.size() % block_size;
280  for(std::size_t i = 0; i < blocks * block_size; i += block_size) {
281  RC4(&cipher_key, block_size, text.data() + i, result.data() + i);
282  }
283  if(extra) {
284  std::size_t i = blocks * block_size;
285  RC4(&cipher_key, extra, text.data() + i, result.data() + i);
286  }
287 #else
288  size_t outWritten = 0;
289  CCCryptorStatus ccStatus = CCCrypt(kCCDecrypt,
290  kCCAlgorithmRC4,
291  kCCOptionPKCS7Padding,
292  key.data(),
293  key.size(),
294  nullptr,
295  text.data(),
296  text.size(),
297  result.data(),
298  result.size(),
299  &outWritten);
300 
301  assert(ccStatus == kCCSuccess);
302  assert(outWritten == text.size());
303 #endif
304  return result;
305 }
306 
308 {
309  return rc4_crypt(text, key);
310 }
311 
313 {
314  auto buf = rc4_crypt(text, key);
315  while(!buf.empty() && buf.back() == 0) {
316  buf.pop_back();
317  }
318  return buf;
319 }
320 
322 {
323  secure_buffer unescaped;
324  unescaped.reserve(text.size());
325  bool escaping = false;
326  for(char c : text) {
327  if(escaping) {
328  if(c == '\xa') {
329  unescaped.push_back('\xc');
330  } else if(c == '.') {
331  unescaped.push_back('@');
332  } else {
333  unescaped.push_back(c);
334  }
335  escaping = false;
336  } else if(c == '\x1') {
337  escaping = true;
338  } else {
339  unescaped.push_back(c);
340  }
341  }
342  assert(!escaping);
343  return unescaped;
344 }
345 
347 {
348  secure_buffer escaped;
349  escaped.reserve(text.size());
350  for(char c : text) {
351  if(c == '\x1') {
352  escaped.push_back('\x1');
353  escaped.push_back('\x1');
354  } else if(c == '\xc') {
355  escaped.push_back('\x1');
356  escaped.push_back('\xa');
357  } else if(c == '@') {
358  escaped.push_back('\x1');
359  escaped.push_back('.');
360  } else {
361  escaped.push_back(c);
362  }
363  }
364  return escaped;
365 }
secure_buffer key
Definition: credentials.cpp:56
login_info(const std::string &username, const std::string &server)
Definition: credentials.cpp:60
static secure_buffer rc4_crypt(const secure_buffer &text, const secure_buffer &key)
bool remember_password()
bool delete_file(const std::string &filename)
Definition: filesystem.cpp:901
static secure_buffer encrypt(const secure_buffer &text, const secure_buffer &key)
void set_remember_password(bool remember)
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:282
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
Definition: filesystem.cpp:920
static secure_buffer unescape(const secure_buffer &text)
STL namespace.
void set(const std::string &key, bool value)
Definition: general.cpp:180
void save_credentials()
static lg::log_domain log_config("config")
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
std::string username
Definition: credentials.cpp:55
static secure_buffer escape(const secure_buffer &text)
static const char * name(const std::vector< SDL_Joystick *> &joysticks, const std::size_t index)
Definition: joystick.cpp:48
static std::string at(const std::string &file, int line)
secure_buffer(T &&... args)
Definition: credentials.cpp:44
std::string get(const std::string &key)
Definition: general.cpp:228
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:86
static void clear_credentials()
Definition: credentials.cpp:99
void set_login(const std::string &login)
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:39
void set_password(const std::string &server, const std::string &login, const std::string &key)
Modify, read and display user preferences.
std::string login()
static const unsigned char CREDENTIAL_SEPARATOR
Definition: credentials.cpp:72
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:40
static std::string get_system_username()
Definition: credentials.cpp:80
std::size_t i
Definition: function.cpp:933
static secure_buffer decrypt(const secure_buffer &text, const secure_buffer &key)
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
static const std::string EMPTY_LOGIN
std::string password(const std::string &server, const std::string &login)
Declarations for File-IO.
static std::vector< login_info > credentials
Definition: credentials.cpp:69
#define ERR_CFG
Definition: credentials.cpp:39
void load_credentials()
std::string get_credentials_file()
Standard logging facilities (interface).
std::size_t size() const
Definition: credentials.cpp:63
login_info(const std::string &username, const std::string &server, const secure_buffer &key)
Definition: credentials.cpp:57
static secure_buffer build_key(const std::string &server, const std::string &login)
mock_char c
filesystem::scoped_ostream ostream_file(const std::string &fname, bool create_directory)
Definition: filesystem.cpp:958