mtproto 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Rakefile +26 -1
  4. data/ext/aes_ige/Makefile +273 -0
  5. data/ext/aes_ige/aes_ige.c +103 -0
  6. data/ext/aes_ige/extconf.rb +25 -0
  7. data/ext/factorization/Makefile +273 -0
  8. data/ext/factorization/extconf.rb +3 -0
  9. data/ext/factorization/factorization.c +62 -0
  10. data/lib/mtproto/auth_key_generator.rb +241 -0
  11. data/lib/mtproto/client.rb +217 -20
  12. data/lib/mtproto/connection.rb +103 -0
  13. data/lib/mtproto/crypto/aes_ige.rb +23 -0
  14. data/lib/mtproto/crypto/auth_key_helper.rb +25 -0
  15. data/lib/mtproto/crypto/dh_key_exchange.rb +44 -0
  16. data/lib/mtproto/crypto/dh_validator.rb +80 -0
  17. data/lib/mtproto/crypto/factorization.rb +39 -0
  18. data/lib/mtproto/crypto/message_key.rb +32 -0
  19. data/lib/mtproto/crypto/rsa_key.rb +9 -15
  20. data/lib/mtproto/crypto/rsa_pad.rb +59 -0
  21. data/lib/mtproto/encrypted_message.rb +86 -0
  22. data/lib/mtproto/errors.rb +33 -0
  23. data/lib/mtproto/session.rb +20 -0
  24. data/lib/mtproto/tl/bad_msg_notification.rb +46 -0
  25. data/lib/mtproto/tl/client_dh_inner_data.rb +29 -0
  26. data/lib/mtproto/tl/code_settings.rb +25 -0
  27. data/lib/mtproto/tl/config.rb +124 -0
  28. data/lib/mtproto/tl/gzip_packed.rb +41 -0
  29. data/lib/mtproto/tl/message.rb +148 -2
  30. data/lib/mtproto/tl/msg_container.rb +40 -0
  31. data/lib/mtproto/tl/new_session_created.rb +30 -0
  32. data/lib/mtproto/tl/p_q_inner_data.rb +41 -0
  33. data/lib/mtproto/tl/rpc_error.rb +34 -0
  34. data/lib/mtproto/tl/sent_code.rb +128 -0
  35. data/lib/mtproto/tl/serializer.rb +55 -0
  36. data/lib/mtproto/tl/server_dh_inner_data.rb +85 -0
  37. data/lib/mtproto/transport/tcp_connection.rb +1 -1
  38. data/lib/mtproto/version.rb +1 -1
  39. data/lib/mtproto.rb +24 -0
  40. data/tmp/.keep +0 -0
  41. metadata +33 -1
@@ -0,0 +1,273 @@
1
+
2
+ SHELL = /bin/sh
3
+
4
+ # V=0 quiet, V=1 verbose. other values don't work.
5
+ V = 0
6
+ V0 = $(V:0=)
7
+ Q1 = $(V:1=)
8
+ Q = $(Q1:0=@)
9
+ ECHO1 = $(V:1=@ :)
10
+ ECHO = $(ECHO1:0=@ echo)
11
+ NULLCMD = :
12
+
13
+ #### Start of system configuration section. ####
14
+
15
+ srcdir = .
16
+ topdir = /Users/alev/.rvm/rubies/ruby-3.4.4/include/ruby-3.4.0
17
+ hdrdir = $(topdir)
18
+ arch_hdrdir = /Users/alev/.rvm/rubies/ruby-3.4.4/include/ruby-3.4.0/arm64-darwin24
19
+ PATH_SEPARATOR = :
20
+ VPATH = $(srcdir):$(arch_hdrdir)/ruby:$(hdrdir)/ruby
21
+ prefix = $(DESTDIR)/Users/alev/.rvm/rubies/ruby-3.4.4
22
+ rubysitearchprefix = $(rubylibprefix)/$(sitearch)
23
+ rubyarchprefix = $(rubylibprefix)/$(arch)
24
+ rubylibprefix = $(libdir)/$(RUBY_BASE_NAME)
25
+ exec_prefix = $(prefix)
26
+ vendorarchhdrdir = $(vendorhdrdir)/$(sitearch)
27
+ sitearchhdrdir = $(sitehdrdir)/$(sitearch)
28
+ rubyarchhdrdir = $(rubyhdrdir)/$(arch)
29
+ vendorhdrdir = $(rubyhdrdir)/vendor_ruby
30
+ sitehdrdir = $(rubyhdrdir)/site_ruby
31
+ rubyhdrdir = $(includedir)/$(RUBY_VERSION_NAME)
32
+ vendorarchdir = $(vendorlibdir)/$(sitearch)
33
+ vendorlibdir = $(vendordir)/$(ruby_version)
34
+ vendordir = $(rubylibprefix)/vendor_ruby
35
+ sitearchdir = $(sitelibdir)/$(sitearch)
36
+ sitelibdir = $(sitedir)/$(ruby_version)
37
+ sitedir = $(rubylibprefix)/site_ruby
38
+ rubyarchdir = $(rubylibdir)/$(arch)
39
+ rubylibdir = $(rubylibprefix)/$(ruby_version)
40
+ sitearchincludedir = $(includedir)/$(sitearch)
41
+ archincludedir = $(includedir)/$(arch)
42
+ sitearchlibdir = $(libdir)/$(sitearch)
43
+ archlibdir = $(libdir)/$(arch)
44
+ ridir = $(datarootdir)/$(RI_BASE_NAME)
45
+ modular_gc_dir = $(DESTDIR)
46
+ mandir = $(datarootdir)/man
47
+ localedir = $(datarootdir)/locale
48
+ libdir = $(exec_prefix)/lib
49
+ psdir = $(docdir)
50
+ pdfdir = $(docdir)
51
+ dvidir = $(docdir)
52
+ htmldir = $(docdir)
53
+ infodir = $(datarootdir)/info
54
+ docdir = $(datarootdir)/doc/$(PACKAGE)
55
+ oldincludedir = $(DESTDIR)/usr/include
56
+ includedir = $(SDKROOT)$(prefix)/include
57
+ runstatedir = $(localstatedir)/run
58
+ localstatedir = $(prefix)/var
59
+ sharedstatedir = $(prefix)/com
60
+ sysconfdir = $(prefix)/etc
61
+ datadir = $(datarootdir)
62
+ datarootdir = $(prefix)/share
63
+ libexecdir = $(exec_prefix)/libexec
64
+ sbindir = $(exec_prefix)/sbin
65
+ bindir = $(exec_prefix)/bin
66
+ archdir = $(rubyarchdir)
67
+
68
+
69
+ CC_WRAPPER =
70
+ CC = gcc
71
+ CXX = g++
72
+ LIBRUBY = $(LIBRUBY_SO)
73
+ LIBRUBY_A = lib$(RUBY_SO_NAME)-static.a
74
+ LIBRUBYARG_SHARED = -l$(RUBY_SO_NAME)
75
+ LIBRUBYARG_STATIC = -l$(RUBY_SO_NAME)-static -framework CoreFoundation $(MAINLIBS)
76
+ empty =
77
+ OUTFLAG = -o $(empty)
78
+ COUTFLAG = -o $(empty)
79
+ CSRCFLAG = $(empty)
80
+
81
+ RUBY_EXTCONF_H =
82
+ cflags = $(hardenflags) -fdeclspec $(optflags) $(debugflags) $(warnflags)
83
+ cxxflags =
84
+ optflags = -O3 -fno-fast-math
85
+ debugflags = -ggdb3
86
+ warnflags = -Wall -Wextra -Wextra-tokens -Wdeprecated-declarations -Wdivision-by-zero -Wdiv-by-zero -Wimplicit-function-declaration -Wimplicit-int -Wpointer-arith -Wshorten-64-to-32 -Wwrite-strings -Wold-style-definition -Wmissing-noreturn -Wno-cast-function-type -Wno-constant-logical-operand -Wno-long-long -Wno-missing-field-initializers -Wno-overlength-strings -Wno-parentheses-equality -Wno-self-assign -Wno-tautological-compare -Wno-unused-parameter -Wno-unused-value -Wunused-variable -Wmisleading-indentation -Wundef
87
+ cppflags =
88
+ CCDLFLAGS = -fno-common
89
+ CFLAGS = $(CCDLFLAGS) -O3 -I/opt/homebrew/opt/libyaml/include -I/opt/homebrew/opt/libksba/include -I/opt/homebrew/opt/readline/include -I/opt/homebrew/opt/zlib/include -I/opt/homebrew/opt/openssl@1.1/include $(cflags) -fno-common -pipe $(ARCH_FLAG)
90
+ INCFLAGS = -I. -I$(arch_hdrdir) -I$(hdrdir)/ruby/backward -I$(hdrdir) -I$(srcdir)
91
+ DEFS =
92
+ CPPFLAGS = -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -D_DARWIN_UNLIMITED_SELECT -D_REENTRANT $(DEFS) $(cppflags)
93
+ CXXFLAGS = $(CCDLFLAGS) -fdeclspec $(ARCH_FLAG)
94
+ ldflags = -L. -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/libksba/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/openssl@1.1/lib -fstack-protector-strong
95
+ dldflags = -L/opt/homebrew/opt/libyaml/lib -L/opt/homebrew/opt/libksba/lib -L/opt/homebrew/opt/readline/lib -L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/openssl@1.1/lib -Wl,-undefined,dynamic_lookup
96
+ ARCH_FLAG = -arch arm64
97
+ DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
98
+ LDSHARED = $(CC) -dynamic -bundle
99
+ LDSHAREDXX = $(CXX) -dynamic -bundle
100
+ POSTLINK = dsymutil $@ 2>/dev/null; { test -z '$(RUBY_CODESIGN)' || codesign -s '$(RUBY_CODESIGN)' $@; }
101
+ AR = ar
102
+ LD = ld
103
+ EXEEXT =
104
+
105
+ RUBY_INSTALL_NAME = $(RUBY_BASE_NAME)
106
+ RUBY_SO_NAME = ruby.3.4
107
+ RUBYW_INSTALL_NAME =
108
+ RUBY_VERSION_NAME = $(RUBY_BASE_NAME)-$(ruby_version)
109
+ RUBYW_BASE_NAME = rubyw
110
+ RUBY_BASE_NAME = ruby
111
+
112
+ arch = arm64-darwin24
113
+ sitearch = $(arch)
114
+ ruby_version = 3.4.0
115
+ ruby = $(bindir)/$(RUBY_BASE_NAME)
116
+ RUBY = $(ruby)
117
+ BUILTRUBY = $(bindir)/$(RUBY_BASE_NAME)
118
+ ruby_headers = $(hdrdir)/ruby.h $(hdrdir)/ruby/backward.h $(hdrdir)/ruby/ruby.h $(hdrdir)/ruby/defines.h $(hdrdir)/ruby/missing.h $(hdrdir)/ruby/intern.h $(hdrdir)/ruby/st.h $(hdrdir)/ruby/subst.h $(arch_hdrdir)/ruby/config.h
119
+
120
+ RM = rm -f
121
+ RM_RF = rm -fr
122
+ RMDIRS = rmdir -p
123
+ MAKEDIRS = /opt/homebrew/opt/coreutils/bin/gmkdir -p
124
+ INSTALL = /opt/homebrew/opt/coreutils/bin/ginstall -c
125
+ INSTALL_PROG = $(INSTALL) -m 0755
126
+ INSTALL_DATA = $(INSTALL) -m 644
127
+ COPY = cp
128
+ TOUCH = exit >
129
+
130
+ #### End of system configuration section. ####
131
+
132
+ preload =
133
+ libpath = . $(libdir)
134
+ LIBPATH = -L. -L$(libdir)
135
+ DEFFILE =
136
+
137
+ CLEANFILES = mkmf.log
138
+ DISTCLEANFILES =
139
+ DISTCLEANDIRS =
140
+
141
+ extout =
142
+ extout_prefix =
143
+ target_prefix = /factorization
144
+ LOCAL_LIBS =
145
+ LIBS = $(LIBRUBYARG_SHARED) -lpthread
146
+ ORIG_SRCS = factorization.c
147
+ SRCS = $(ORIG_SRCS)
148
+ OBJS = factorization.o
149
+ HDRS =
150
+ LOCAL_HDRS =
151
+ TARGET = factorization
152
+ TARGET_NAME = factorization
153
+ TARGET_ENTRY = Init_$(TARGET_NAME)
154
+ DLLIB = $(TARGET).bundle
155
+ EXTSTATIC =
156
+ STATIC_LIB =
157
+
158
+ TIMESTAMP_DIR = .
159
+ BINDIR = $(bindir)
160
+ RUBYCOMMONDIR = $(sitedir)$(target_prefix)
161
+ RUBYLIBDIR = $(sitelibdir)$(target_prefix)
162
+ RUBYARCHDIR = $(sitearchdir)$(target_prefix)
163
+ HDRDIR = $(sitehdrdir)$(target_prefix)
164
+ ARCHHDRDIR = $(sitearchhdrdir)$(target_prefix)
165
+ TARGET_SO_DIR =
166
+ TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
167
+ CLEANLIBS = $(TARGET_SO) $(TARGET_SO:=.dSYM)
168
+ CLEANOBJS = $(OBJS) *.bak
169
+ TARGET_SO_DIR_TIMESTAMP = $(TIMESTAMP_DIR)/.sitearchdir.-.factorization.time
170
+
171
+ all: $(DLLIB)
172
+ static: $(STATIC_LIB)
173
+ .PHONY: all install static install-so install-rb
174
+ .PHONY: clean clean-so clean-static clean-rb
175
+
176
+ clean-static::
177
+ clean-rb-default::
178
+ clean-rb::
179
+ clean-so::
180
+ clean: clean-so clean-static clean-rb-default clean-rb
181
+ -$(Q)$(RM_RF) $(CLEANLIBS) $(CLEANOBJS) $(CLEANFILES) .*.time
182
+
183
+ distclean-rb-default::
184
+ distclean-rb::
185
+ distclean-so::
186
+ distclean-static::
187
+ distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
188
+ -$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) conftest.* mkmf.log
189
+ -$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES)
190
+ -$(Q)$(RMDIRS) $(DISTCLEANDIRS) 2> /dev/null || true
191
+
192
+ realclean: distclean
193
+ install: install-so install-rb
194
+
195
+ install-so: $(DLLIB) $(TARGET_SO_DIR_TIMESTAMP)
196
+ $(INSTALL_PROG) $(DLLIB) $(RUBYARCHDIR)
197
+ clean-static::
198
+ -$(Q)$(RM) $(STATIC_LIB)
199
+ install-rb: pre-install-rb do-install-rb install-rb-default
200
+ install-rb-default: pre-install-rb-default do-install-rb-default
201
+ pre-install-rb: Makefile
202
+ pre-install-rb-default: Makefile
203
+ do-install-rb:
204
+ do-install-rb-default:
205
+ pre-install-rb-default:
206
+ @$(NULLCMD)
207
+ $(TARGET_SO_DIR_TIMESTAMP):
208
+ $(Q) $(MAKEDIRS) $(@D) $(RUBYARCHDIR)
209
+ $(Q) $(TOUCH) $@
210
+
211
+ site-install: site-install-so site-install-rb
212
+ site-install-so: install-so
213
+ site-install-rb: install-rb
214
+
215
+ .SUFFIXES: .c .m .cc .mm .cxx .cpp .o .S
216
+
217
+ .cc.o:
218
+ $(ECHO) compiling $(<)
219
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
220
+
221
+ .cc.S:
222
+ $(ECHO) translating $(<)
223
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
224
+
225
+ .mm.o:
226
+ $(ECHO) compiling $(<)
227
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
228
+
229
+ .mm.S:
230
+ $(ECHO) translating $(<)
231
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
232
+
233
+ .cxx.o:
234
+ $(ECHO) compiling $(<)
235
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
236
+
237
+ .cxx.S:
238
+ $(ECHO) translating $(<)
239
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
240
+
241
+ .cpp.o:
242
+ $(ECHO) compiling $(<)
243
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
244
+
245
+ .cpp.S:
246
+ $(ECHO) translating $(<)
247
+ $(Q) $(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
248
+
249
+ .c.o:
250
+ $(ECHO) compiling $(<)
251
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
252
+
253
+ .c.S:
254
+ $(ECHO) translating $(<)
255
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
256
+
257
+ .m.o:
258
+ $(ECHO) compiling $(<)
259
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<
260
+
261
+ .m.S:
262
+ $(ECHO) translating $(<)
263
+ $(Q) $(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -S $(CSRCFLAG)$<
264
+
265
+ $(TARGET_SO): $(OBJS) Makefile
266
+ $(ECHO) linking shared-object factorization/$(DLLIB)
267
+ -$(Q)$(RM) $(@)
268
+ $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
269
+ $(Q) $(POSTLINK)
270
+
271
+
272
+
273
+ $(OBJS): $(HDRS) $(ruby_headers)
@@ -0,0 +1,3 @@
1
+ require 'mkmf'
2
+
3
+ create_makefile('factorization/factorization')
@@ -0,0 +1,62 @@
1
+ #include <ruby.h>
2
+ #include <stdint.h>
3
+ #include <math.h>
4
+
5
+ static VALUE mMTProto;
6
+ static VALUE mCrypto;
7
+ static VALUE mFactorization;
8
+
9
+ static VALUE
10
+ factorize_pq(VALUE self, VALUE pq_bytes)
11
+ {
12
+ Check_Type(pq_bytes, T_STRING);
13
+
14
+ long pq_len = RSTRING_LEN(pq_bytes);
15
+ unsigned char *pq_ptr = (unsigned char *)RSTRING_PTR(pq_bytes);
16
+
17
+ uint64_t n = 0;
18
+ for (long i = 0; i < pq_len; i++) {
19
+ n = (n << 8) | pq_ptr[i];
20
+ }
21
+
22
+ if (n <= 3) {
23
+ rb_raise(rb_eArgError, "Number must be > 3");
24
+ }
25
+
26
+ if (n % 2 == 0) {
27
+ VALUE result = rb_ary_new2(2);
28
+ rb_ary_push(result, ULL2NUM(2));
29
+ rb_ary_push(result, ULL2NUM(n / 2));
30
+ return result;
31
+ }
32
+
33
+ uint64_t limit = (uint64_t)sqrt((double)n) + 1;
34
+ for (uint64_t i = 3; i < limit; i += 2) {
35
+ if (n % i == 0) {
36
+ uint64_t p = i;
37
+ uint64_t q = n / i;
38
+
39
+ if (p > q) {
40
+ uint64_t tmp = p;
41
+ p = q;
42
+ q = tmp;
43
+ }
44
+
45
+ VALUE result = rb_ary_new2(2);
46
+ rb_ary_push(result, ULL2NUM(p));
47
+ rb_ary_push(result, ULL2NUM(q));
48
+ return result;
49
+ }
50
+ }
51
+
52
+ rb_raise(rb_eRuntimeError, "No non-trivial factors found (n might be prime)");
53
+ }
54
+
55
+ void Init_factorization(void)
56
+ {
57
+ mMTProto = rb_define_module("MTProto");
58
+ mCrypto = rb_define_module_under(mMTProto, "Crypto");
59
+ mFactorization = rb_define_module_under(mCrypto, "FactorizationExt");
60
+
61
+ rb_define_singleton_method(mFactorization, "factorize_pq", factorize_pq, 1);
62
+ }
@@ -0,0 +1,241 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require 'digest'
5
+
6
+ module MTProto
7
+ class AuthKeyGenerator
8
+ attr_reader :connection, :auth_key, :server_salt, :time_offset, :timeout
9
+
10
+ def initialize(connection, public_key, dc_number = nil, test_mode: false, timeout: 10)
11
+ raise ArgumentError, "public_key is required" if public_key.nil? || public_key.empty?
12
+ raise ArgumentError, "dc_number must be positive. Use test_mode: true for test DCs" if dc_number && dc_number < 0
13
+
14
+ @connection = connection
15
+ @public_key = public_key
16
+ @dc_number = dc_number
17
+ @test_mode = test_mode
18
+ @timeout = timeout
19
+ @auth_key = nil
20
+ @server_salt = nil
21
+ @time_offset = 0
22
+ end
23
+
24
+ def generate
25
+ res_pq = req_pq_multi
26
+ server_key = find_server_key(res_pq[:fingerprints])
27
+ p, q = Crypto::Factorization.factorize_pq(res_pq[:pq])
28
+ new_nonce = SecureRandom.random_bytes(32)
29
+
30
+ encrypted_data = encrypt_pq_inner_data(res_pq, p, q, server_key, new_nonce)
31
+ server_dh_params = send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
32
+ server_dh_inner_data = decrypt_server_dh_params(res_pq, new_nonce, server_dh_params)
33
+
34
+ Crypto::DHValidator.validate_dh_params(
35
+ server_dh_inner_data.g,
36
+ server_dh_inner_data.dh_prime,
37
+ server_dh_inner_data.g_a
38
+ )
39
+
40
+ client_dh_params = Crypto::DHKeyExchange.generate_client_dh_params(
41
+ server_dh_inner_data.g,
42
+ server_dh_inner_data.dh_prime
43
+ )
44
+
45
+ auth_key = Crypto::DHKeyExchange.compute_auth_key(
46
+ server_dh_inner_data.g_a,
47
+ client_dh_params[:b],
48
+ server_dh_inner_data.dh_prime
49
+ )
50
+
51
+ tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq[:server_nonce])
52
+ tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq[:server_nonce])
53
+
54
+ dh_gen_response = send_client_dh_params(
55
+ res_pq,
56
+ new_nonce,
57
+ client_dh_params,
58
+ tmp_aes_key,
59
+ tmp_aes_iv
60
+ )
61
+
62
+ verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
63
+
64
+ @auth_key = auth_key
65
+ @server_salt = compute_server_salt(new_nonce, res_pq[:server_nonce])
66
+ @time_offset = server_dh_inner_data.server_time - Time.now.to_i
67
+
68
+ {
69
+ auth_key: @auth_key,
70
+ server_salt: @server_salt,
71
+ time_offset: @time_offset,
72
+ server_dh_params: server_dh_params,
73
+ server_dh_inner_data: server_dh_inner_data,
74
+ client_dh_params: client_dh_params,
75
+ dh_gen_response: dh_gen_response
76
+ }
77
+ end
78
+
79
+ private
80
+
81
+ def req_pq_multi
82
+ nonce = SecureRandom.random_bytes(16)
83
+ message = TL::Message.req_pq_multi(nonce)
84
+ @connection.send(message.serialize)
85
+
86
+ response_data = @connection.recv(timeout: @timeout)
87
+ response_message = TL::Message.deserialize(response_data)
88
+ res_pq = response_message.parse_res_pq
89
+
90
+ raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
91
+
92
+ res_pq
93
+ end
94
+
95
+ def find_server_key(fingerprints)
96
+ server_key = Crypto::RSAKey.find_by_fingerprint(fingerprints, @public_key)
97
+ raise 'No matching RSA key found!' unless server_key
98
+
99
+ server_key
100
+ end
101
+
102
+ def encrypt_pq_inner_data(res_pq, p, q, server_key, new_nonce)
103
+ raise ArgumentError, "dc_number is required for auth key generation" if @dc_number.nil?
104
+
105
+ # For test DCs, add 10000 to the DC number per MTProto spec
106
+ # For production DCs, use the DC number as-is
107
+ dc_value = @test_mode ? (@dc_number + 10000) : @dc_number
108
+
109
+ inner_data = TL::PQInnerData.new(
110
+ pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
111
+ p: p,
112
+ q: q,
113
+ nonce: res_pq[:nonce],
114
+ server_nonce: res_pq[:server_nonce],
115
+ new_nonce: new_nonce,
116
+ dc: dc_value
117
+ )
118
+
119
+ Crypto::RSA_PAD.encrypt(inner_data.serialize, server_key)
120
+ end
121
+
122
+ def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
123
+ message = TL::Message.req_DH_params(
124
+ nonce: res_pq[:nonce],
125
+ server_nonce: res_pq[:server_nonce],
126
+ p: p,
127
+ q: q,
128
+ public_key_fingerprint: server_key.fingerprint,
129
+ encrypted_data: encrypted_data
130
+ )
131
+
132
+ @connection.send(message.serialize)
133
+
134
+ response_data = @connection.recv(timeout: @timeout)
135
+ response_message = TL::Message.deserialize(response_data)
136
+ server_dh_params = response_message.parse_server_DH_params_ok
137
+
138
+ raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
139
+ raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
140
+
141
+ server_dh_params
142
+ end
143
+
144
+ def decrypt_server_dh_params(res_pq, new_nonce, server_dh_params)
145
+ tmp_aes_key = Crypto::AuthKeyHelper.derive_tmp_aes_key(new_nonce, res_pq[:server_nonce])
146
+ tmp_aes_iv = Crypto::AuthKeyHelper.derive_tmp_aes_iv(new_nonce, res_pq[:server_nonce])
147
+
148
+ answer_with_hash = Crypto::AES_IGE.decrypt_ige(
149
+ server_dh_params[:encrypted_answer],
150
+ tmp_aes_key,
151
+ tmp_aes_iv
152
+ )
153
+
154
+ answer_hash = answer_with_hash[0, 20]
155
+ answer = answer_with_hash[20..]
156
+
157
+ loop do
158
+ computed_hash = Digest::SHA1.digest(answer)
159
+ break if answer_hash == computed_hash
160
+
161
+ raise 'Answer hash mismatch!' if answer.bytesize <= 1
162
+
163
+ answer = answer[0..-2]
164
+ end
165
+
166
+ server_dh_inner_data = TL::ServerDHInnerData.deserialize(answer)
167
+
168
+ raise 'Nonce mismatch in DH inner data!' unless server_dh_inner_data.nonce == res_pq[:nonce]
169
+ raise 'Server nonce mismatch in DH inner data!' unless server_dh_inner_data.server_nonce == res_pq[:server_nonce]
170
+
171
+ server_dh_inner_data
172
+ end
173
+
174
+ def send_client_dh_params(res_pq, new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
175
+ client_dh_inner_data = TL::ClientDHInnerData.new(
176
+ nonce: res_pq[:nonce],
177
+ server_nonce: res_pq[:server_nonce],
178
+ retry_id: 0,
179
+ g_b: client_dh_params[:g_b_bytes]
180
+ )
181
+
182
+ client_dh_data = client_dh_inner_data.serialize
183
+ client_dh_data_with_hash = Digest::SHA1.digest(client_dh_data) + client_dh_data
184
+
185
+ padding_length = (16 - (client_dh_data_with_hash.bytesize % 16)) % 16
186
+ client_dh_data_with_hash += SecureRandom.random_bytes(padding_length) if padding_length.positive?
187
+
188
+ client_dh_encrypted = Crypto::AES_IGE.encrypt_ige(
189
+ client_dh_data_with_hash,
190
+ tmp_aes_key,
191
+ tmp_aes_iv
192
+ )
193
+
194
+ message = TL::Message.set_client_DH_params(
195
+ nonce: res_pq[:nonce],
196
+ server_nonce: res_pq[:server_nonce],
197
+ encrypted_data: client_dh_encrypted
198
+ )
199
+
200
+ @connection.send(message.serialize)
201
+
202
+ response_data = @connection.recv(timeout: @timeout)
203
+ response_message = TL::Message.deserialize(response_data)
204
+ response_message.parse_dh_gen_response
205
+ end
206
+
207
+ def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
208
+ raise 'Nonce mismatch in DH response!' unless dh_gen_response[:nonce] == res_pq[:nonce]
209
+ raise 'Server nonce mismatch in DH response!' unless dh_gen_response[:server_nonce] == res_pq[:server_nonce]
210
+
211
+ auth_key_hash = Digest::SHA1.digest(auth_key)
212
+ auth_key_aux_hash = auth_key_hash[0, 8]
213
+
214
+ expected_new_nonce_hash = case dh_gen_response[:status]
215
+ when :ok
216
+ Digest::SHA1.digest(new_nonce + "\x01".b + auth_key_aux_hash)[-16..]
217
+ when :retry
218
+ Digest::SHA1.digest(new_nonce + "\x02".b + auth_key_aux_hash)[-16..]
219
+ when :fail
220
+ Digest::SHA1.digest(new_nonce + "\x03".b + auth_key_aux_hash)[-16..]
221
+ end
222
+
223
+ raise 'new_nonce_hash mismatch!' unless dh_gen_response[:new_nonce_hash] == expected_new_nonce_hash
224
+
225
+ case dh_gen_response[:status]
226
+ when :retry
227
+ raise 'Server requested retry - not implemented'
228
+ when :fail
229
+ raise 'Server reported DH generation failure'
230
+ end
231
+ end
232
+
233
+ def compute_server_salt(new_nonce, server_nonce)
234
+ server_salt_bytes = String.new(capacity: 8)
235
+ 8.times do |i|
236
+ server_salt_bytes << (new_nonce[i].ord ^ server_nonce[i].ord)
237
+ end
238
+ server_salt_bytes.unpack1('Q<')
239
+ end
240
+ end
241
+ end