Commit 86412402855ef0d5487b83be760957d7b8d8ba0d
1 parent
9d3425b4
Implement RSA login (#9)
Showing
5 changed files
with
194 additions
and
53 deletions
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
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 | ... | ... |