Commit 86412402855ef0d5487b83be760957d7b8d8ba0d

Authored by Matti Virkkunen
1 parent 9d3425b4

Implement RSA login (#9)

README.md
... ... @@ -40,7 +40,7 @@ Install from source (Arch Linux)
40 40  
41 41 Arch Linux packages all the required dependencies, so you can install the plugin by simply typing:
42 42  
43   - sudo pacman -S thrift libpurple
  43 + sudo pacman -S thrift libpurple libgcrypt
44 44 make
45 45 sudo make install
46 46  
... ... @@ -50,9 +50,10 @@ Install from source (any distribution)
50 50 Make sure you have the required prerequisites installed:
51 51  
52 52 * libpurple - Library that provides the core functionality of Pidgin and other compatible clients.
53   - Probably available from your package manager
  53 + Probably available from your package manager.
54 54 * thrift - Apache Thrift compiler. May be available from your package manager.
55 55 * libthrift - Apache Thrift C++ library. May be available from your package manager.
  56 +* libgcrypt - Crypto library. Probably available from your package manager.
56 57  
57 58 To install the plugin system-wide, run:
58 59  
... ...
libpurple/Makefile
... ... @@ -14,9 +14,9 @@ endif
14 14 CXX ?= g++
15 15 CXXFLAGS = -g -Wall -shared -fPIC \
16 16 -DHAVE_INTTYPES_H -DHAVE_CONFIG_H -DPURPLE_PLUGINS \
17   - `pkg-config --cflags purple` $(THRIFT_CXXFLAGS)
  17 + `pkg-config --cflags purple` `libgcrypt-config --cflags` $(THRIFT_CXXFLAGS)
18 18  
19   -LIBS = `pkg-config --libs purple` $(THRIFT_LIBS)
  19 +LIBS = `pkg-config --libs purple` `libgcrypt-config --libs` $(THRIFT_LIBS)
20 20  
21 21 PURPLE_PLUGIN_DIR:=$(shell pkg-config --variable=plugindir purple)
22 22 PURPLE_DATA_ROOT_DIR:=$(shell pkg-config --variable=datarootdir purple)
... ...
libpurple/line.thrift
... ... @@ -248,6 +248,13 @@ struct Room {
248 248 10: list<Contact> contacts;
249 249 }
250 250  
  251 +struct RSAKey {
  252 + 1: string keynm;
  253 + 2: string nvalue;
  254 + 3: string evalue;
  255 + 4: string sessionKey;
  256 +}
  257 +
251 258 exception TalkException {
252 259 1: ErrorCode code;
253 260 2: string reason;
... ... @@ -298,6 +305,9 @@ service TalkService {
298 305 Room getRoom(
299 306 2: string roomId) throws(1: TalkException e);
300 307  
  308 + RSAKey getRSAKeyInfo(
  309 + 2: IdentityProvider provider) throws(1: TalkException e);
  310 +
301 311 LoginResult loginWithIdentityCredentialForCertificate(
302 312 8: IdentityProvider identityProvider,
303 313 3: string identifier,
... ...
libpurple/purpleline.hpp
... ... @@ -166,6 +166,7 @@ private:
166 166 void login_start();
167 167  
168 168 void get_auth_token();
  169 + std::string get_encrypted_credentials(line::RSAKey &key);
169 170 void set_auth_token(std::string auth_token);
170 171 void get_last_op_revision();
171 172 void get_profile();
... ...
libpurple/purpleline_login.cpp
1 1 #include "purpleline.hpp"
2 2  
  3 +#include <sstream>
  4 +#include <iomanip>
  5 +
3 6 #include <core.h>
4 7  
  8 +#include <gcrypt.h>
  9 +
  10 +static std::string hex_to_bytes(std::string hex) {
  11 + if (hex.size() % 2 != 0)
  12 + hex = std::string("0") + hex;
  13 +
  14 + std::string result(hex.size() / 2, '\0');
  15 + for (size_t i = 0; i < result.size(); i++)
  16 + result[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16);
  17 +
  18 + return result;
  19 +}
  20 +
  21 +static std::string bytes_to_hex(std::string bytes) {
  22 + std::ostringstream ss;
  23 +
  24 + for (size_t i = 0; i < bytes.size(); i++)
  25 + ss << std::hex << std::setfill('0') << std::setw(2) << (unsigned)(bytes[i] & 0xff);
  26 +
  27 + return ss.str();
  28 +}
  29 +
5 30 void PurpleLine::login_start() {
6 31 purple_connection_set_state(conn, PURPLE_CONNECTING);
7 32 purple_connection_update_progress(conn, "Logging in", 0, 3);
... ... @@ -51,35 +76,21 @@ void PurpleLine::login_start() {
51 76 }
52 77  
53 78 void PurpleLine::get_auth_token() {
54   - std::string certificate(purple_account_get_string(acct, LINE_ACCOUNT_CERTIFICATE, ""));
55   -
56 79 purple_debug_info("line", "Logging in with credentials to get new auth token.\n");
57 80  
58   - std::string ui_name = "purple-line";
59   -
60   - GHashTable *ui_info = purple_core_get_ui_info();
61   - gpointer ui_name_p = g_hash_table_lookup(ui_info, "name");
62   - if (ui_name_p)
63   - ui_name = (char *)ui_name_p;
64   -
65   - c_out->send_loginWithIdentityCredentialForCertificate(
66   - line::IdentityProvider::LINE,
67   - purple_account_get_username(acct),
68   - purple_account_get_password(acct),
69   - true,
70   - "127.0.0.1",
71   - ui_name,
72   - certificate);
  81 + c_out->send_getRSAKeyInfo(line::IdentityProvider::LINE);
73 82 c_out->send([this]() {
74   - line::LoginResult result;
  83 + line::RSAKey key;
  84 + std::string credentials;
75 85  
76 86 try {
77   - c_out->recv_loginWithIdentityCredentialForCertificate(result);
78   - } catch (line::TalkException &err) {
79   - std::string msg = "Could not log in. " + err.reason;
  87 + c_out->recv_getRSAKeyInfo(key);
80 88  
81   - conn->wants_to_die = TRUE;
  89 + credentials = get_encrypted_credentials(key);
  90 + } catch (std::exception &ex) {
  91 + std::string msg = std::string("Could not log in. ") + ex.what();
82 92  
  93 + conn->wants_to_die = TRUE;
83 94 purple_connection_error(
84 95 conn,
85 96 msg.c_str());
... ... @@ -87,42 +98,160 @@ void PurpleLine::get_auth_token() {
87 98 return;
88 99 }
89 100  
90   - if (result.type == line::LoginResultType::SUCCESS && result.authToken != "")
91   - {
92   - set_auth_token(result.authToken);
  101 + std::string certificate(purple_account_get_string(acct, LINE_ACCOUNT_CERTIFICATE, ""));
93 102  
94   - get_last_op_revision();
95   - }
96   - else if (result.type == line::LoginResultType::REQUIRE_DEVICE_CONFIRM)
97   - {
98   - purple_debug_info("line", "Starting PIN verification.\n");
  103 + std::string ui_name = "purple-line";
99 104  
100   - pin_verifier.verify(result, [this](std::string auth_token, std::string certificate) {
101   - if (certificate != "") {
102   - purple_account_set_string(
103   - acct,
104   - LINE_ACCOUNT_CERTIFICATE,
105   - certificate.c_str());
106   - }
  105 + GHashTable *ui_info = purple_core_get_ui_info();
  106 + gpointer ui_name_p = g_hash_table_lookup(ui_info, "name");
  107 + if (ui_name_p)
  108 + ui_name = (char *)ui_name_p;
  109 +
  110 + c_out->send_loginWithIdentityCredentialForCertificate(
  111 + line::IdentityProvider::LINE,
  112 + key.keynm,
  113 + credentials,
  114 + true,
  115 + "127.0.0.1",
  116 + ui_name,
  117 + certificate);
  118 + c_out->send([this]() {
  119 + line::LoginResult result;
107 120  
108   - set_auth_token(auth_token);
  121 + try {
  122 + c_out->recv_loginWithIdentityCredentialForCertificate(result);
  123 + } catch (line::TalkException &err) {
  124 + std::string msg = "Could not log in. " + err.reason;
  125 +
  126 + conn->wants_to_die = TRUE;
  127 + purple_connection_error(
  128 + conn,
  129 + msg.c_str());
  130 +
  131 + return;
  132 + }
  133 +
  134 + if (result.type == line::LoginResultType::SUCCESS && result.authToken != "")
  135 + {
  136 + set_auth_token(result.authToken);
109 137  
110 138 get_last_op_revision();
111   - });
112   - }
113   - else
114   - {
115   - std::stringstream ss("Could not log in. Bad LoginResult type: ");
116   - ss << result.type;
117   - std::string msg = ss.str();
  139 + }
  140 + else if (result.type == line::LoginResultType::REQUIRE_DEVICE_CONFIRM)
  141 + {
  142 + purple_debug_info("line", "Starting PIN verification.\n");
118 143  
119   - purple_connection_error(
120   - conn,
121   - msg.c_str());
122   - }
  144 + pin_verifier.verify(result, [this](std::string auth_token, std::string certificate) {
  145 + if (certificate != "") {
  146 + purple_account_set_string(
  147 + acct,
  148 + LINE_ACCOUNT_CERTIFICATE,
  149 + certificate.c_str());
  150 + }
  151 +
  152 + set_auth_token(auth_token);
  153 +
  154 + get_last_op_revision();
  155 + });
  156 + }
  157 + else
  158 + {
  159 + std::stringstream ss("Could not log in. Bad LoginResult type: ");
  160 + ss << result.type;
  161 + std::string msg = ss.str();
  162 +
  163 + purple_connection_error(
  164 + conn,
  165 + msg.c_str());
  166 + }
  167 + });
123 168 });
124 169 }
125 170  
  171 +// This may throw.
  172 +std::string PurpleLine::get_encrypted_credentials(line::RSAKey &key) {
  173 + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
  174 + if (!gcry_check_version(GCRYPT_VERSION))
  175 + throw new std::runtime_error("libgcrypt version mismatch.");
  176 +
  177 + gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
  178 +
  179 + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
  180 + }
  181 +
  182 + std::string username = purple_account_get_username(acct);
  183 + std::string password = purple_account_get_password(acct);
  184 +
  185 + if (username.size() > 255)
  186 + throw std::runtime_error("Username is too long.");
  187 +
  188 + if (password.size() > 255)
  189 + throw std::runtime_error("Password is too long.");
  190 +
  191 + std::ostringstream buf;
  192 +
  193 + buf << (char)key.sessionKey.size() << key.sessionKey;
  194 + buf << (char)username.size() << username;
  195 + buf << (char)password.size() << password;
  196 +
  197 + std::string modulus = hex_to_bytes(key.nvalue);
  198 + std::string exponent = hex_to_bytes(key.evalue);
  199 +
  200 + std::string credentials = buf.str();
  201 +
  202 + gpg_error_t err;
  203 +
  204 + gcry_sexp_t public_key, plaintext, ciphertext, result;
  205 +
  206 + err = gcry_sexp_build(
  207 + &public_key, nullptr,
  208 + "(public-key (rsa (n %b) (e %b)))",
  209 + (int)modulus.size(), modulus.c_str(),
  210 + (int)exponent.size(), exponent.c_str());
  211 +
  212 + if (err)
  213 + throw std::runtime_error(std::string("ligbcrypt public key error: ") + gpg_strerror(err));
  214 +
  215 + err = gcry_sexp_build(
  216 + &plaintext, nullptr,
  217 + "(data (flags pkcs1) (value %b))",
  218 + (int)credentials.size(), credentials.c_str());
  219 +
  220 + if (err) {
  221 + gcry_sexp_release(public_key);
  222 + throw std::runtime_error(std::string("ligbcrypt data error: ") + gpg_strerror(err));
  223 + }
  224 +
  225 + err = gcry_pk_encrypt(&ciphertext, plaintext, public_key);
  226 +
  227 + gcry_sexp_release(plaintext);
  228 + gcry_sexp_release(public_key);
  229 +
  230 + if (err)
  231 + throw std::runtime_error(std::string("libgcrypt encryption error: ") + gpg_strerror(err));
  232 +
  233 + result = gcry_sexp_find_token(ciphertext, "a", 0);
  234 +
  235 + gcry_sexp_release(ciphertext);
  236 +
  237 + if (!result)
  238 + throw std::runtime_error("libgcrypt result token not found");
  239 +
  240 + size_t result_data_len;
  241 + const char *result_data = gcry_sexp_nth_data(result, 1, &result_data_len);
  242 +
  243 + if (!result_data) {
  244 + gcry_sexp_release(result);
  245 + throw std::runtime_error("libgcrypt result token value not found");
  246 + }
  247 +
  248 + std::string result_bytes(result_data, result_data_len);
  249 +
  250 + gcry_sexp_release(result);
  251 +
  252 + return bytes_to_hex(result_bytes);
  253 +}
  254 +
126 255 void PurpleLine::set_auth_token(std::string auth_token) {
127 256 purple_account_set_string(acct, LINE_ACCOUNT_AUTH_TOKEN, auth_token.c_str());
128 257  
... ...