mtproto 0.0.3 → 0.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa11aaca7c34af33346fa59458bf300211785023ff833336738b0988deeafc32
4
- data.tar.gz: 417cef055fd9d042956880f8e0214a10669759d7881e3af67e15e3bd2018de6c
3
+ metadata.gz: 414d6f7ed0a5c1c20c5108844469daafff2af7d2c350c8370b2eb6cec73068fe
4
+ data.tar.gz: d7c8e3397861bdc4f9d3c695ba884a672698f39c051c06271e5e39dcdf0aa7bd
5
5
  SHA512:
6
- metadata.gz: 630dadd58d377ed2342abddf659385e8864fbfca3e6c4e1e9fa5a861b29a7a4fcb7628f9e3c7341c716588c5b37c18944d14e954cf0304ced52fa8482dcf859b
7
- data.tar.gz: 796286ff4a5b9b9a42d7b0c14616040f3b8e0b310c2afece12943f49c14622d1f82f5b4479ef23b47fa6cabd3216b95aa16d0eb1ba4459cddab039042b94dd2a
6
+ metadata.gz: 2ccbe16eaf853697c6267ebe6b84e8593c071a66d95de9b1a1be1cafb0e33317400fb52fe85ef7aec43da2aa69bbc3f4cbc55af25928116711518925f4ed7897
7
+ data.tar.gz: 9658e4c8bd82fed68fd43f40e35efb1a57742170d27aa5f6a641528628e6da0013dfa9ddb632f42cb6e5418cd82c3006b8a4624294ebc40567c08d636aeae96b
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'transport/tcp_connection'
5
+ require_relative 'transport/abridged_packet_codec'
6
+ require_relative 'tl/message'
7
+
8
+ module MTProto
9
+ class Client
10
+ DC_ADDRESSES = {
11
+ 1 => ['149.154.175.50', 443],
12
+ 2 => ['149.154.167.51', 443],
13
+ 3 => ['149.154.175.100', 443],
14
+ 4 => ['149.154.167.91', 443],
15
+ 5 => ['91.108.56.130', 443]
16
+ }.freeze
17
+
18
+ attr_reader :connection
19
+
20
+ def initialize(dc_id: 2)
21
+ host, port = DC_ADDRESSES[dc_id]
22
+ raise ArgumentError, "Unknown DC ID: #{dc_id}" unless host
23
+
24
+ codec = Transport::AbridgedPacketCodec.new
25
+ @connection = Transport::TCPConnection.new(host, port, codec)
26
+ end
27
+
28
+ def connect
29
+ @connection.connect
30
+ end
31
+
32
+ def disconnect
33
+ @connection.close
34
+ end
35
+
36
+ def req_pq_multi
37
+ nonce = SecureRandom.random_bytes(16)
38
+
39
+ message = TL::Message.req_pq_multi(nonce)
40
+ payload = message.serialize
41
+
42
+ @connection.send(payload)
43
+
44
+ response_data = @connection.recv(timeout: 10)
45
+ response_message = TL::Message.deserialize(response_data)
46
+
47
+ res_pq = response_message.parse_res_pq
48
+
49
+ raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
50
+
51
+ rsa_key = Crypto::RSAKey.find_by_fingerprint(res_pq[:fingerprints])
52
+ raise 'No matching RSA key found!' unless rsa_key
53
+
54
+ res_pq[:rsa_key] = rsa_key
55
+ res_pq
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'digest'
5
+
6
+ module MTProto
7
+ module Crypto
8
+ class RSAKey
9
+ TELEGRAM_KEY = <<~PEM
10
+ -----BEGIN RSA PUBLIC KEY-----
11
+ MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g
12
+ 5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO
13
+ 62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/
14
+ +aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9
15
+ t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs
16
+ 5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB
17
+ -----END RSA PUBLIC KEY-----
18
+ PEM
19
+
20
+ attr_reader :key, :fingerprint
21
+
22
+ def initialize(pem_string)
23
+ @key = OpenSSL::PKey::RSA.new(pem_string)
24
+ @fingerprint = calculate_fingerprint
25
+ end
26
+
27
+ def self.telegram_key
28
+ @telegram_key ||= new(TELEGRAM_KEY)
29
+ end
30
+
31
+ def self.find_by_fingerprint(fingerprints)
32
+ telegram_key if fingerprints.include?(telegram_key.fingerprint)
33
+ end
34
+
35
+ private
36
+
37
+ def calculate_fingerprint
38
+ n_bytes = @key.n.to_s(2).unpack1('B*').scan(/.{8}/).map { |b| b.to_i(2) }.pack('C*')
39
+ e_bytes = @key.e.to_s(2).unpack1('B*').scan(/.{8}/).map { |b| b.to_i(2) }.pack('C*')
40
+
41
+ n_tl = serialize_bytes(n_bytes)
42
+ e_tl = serialize_bytes(e_bytes)
43
+
44
+ data = n_tl + e_tl
45
+ sha1 = Digest::SHA1.digest(data)
46
+
47
+ sha1[-8..].unpack1('Q<')
48
+ end
49
+
50
+ def serialize_bytes(bytes)
51
+ length = bytes.bytesize
52
+
53
+ if length <= 253
54
+ [length].pack('C') + bytes + padding(length + 1)
55
+ else
56
+ [254].pack('C') + [length].pack('L<')[0, 3] + bytes + padding(length + 4)
57
+ end
58
+ end
59
+
60
+ def padding(current_length)
61
+ pad_length = (4 - (current_length % 4)) % 4
62
+ "\x00" * pad_length
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class Message
6
+ CONSTRUCTOR_REQ_PQ_MULTI = 0xbe7e8ef1
7
+ CONSTRUCTOR_RES_PQ = 0x05162463
8
+
9
+ attr_reader :auth_key_id, :msg_id, :body
10
+
11
+ def initialize(auth_key_id: 0, msg_id: nil, body: '')
12
+ @auth_key_id = auth_key_id
13
+ @msg_id = msg_id || generate_msg_id
14
+ @body = body
15
+ end
16
+
17
+ def serialize
18
+ auth_key_id_bytes = [@auth_key_id].pack('Q<')
19
+ msg_id_bytes = [@msg_id].pack('Q<')
20
+ body_length = [@body.bytesize].pack('L<')
21
+
22
+ auth_key_id_bytes + msg_id_bytes + body_length + @body
23
+ end
24
+
25
+ def self.req_pq_multi(nonce)
26
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
27
+
28
+ constructor = [CONSTRUCTOR_REQ_PQ_MULTI].pack('L<')
29
+ body = constructor + nonce
30
+
31
+ new(auth_key_id: 0, body: body)
32
+ end
33
+
34
+ def self.deserialize(data)
35
+ auth_key_id = data[0, 8].unpack1('Q<')
36
+ msg_id = data[8, 8].unpack1('Q<')
37
+ body_length = data[16, 4].unpack1('L<')
38
+ body = data[20, body_length]
39
+
40
+ new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
41
+ end
42
+
43
+ def parse_res_pq
44
+ constructor = @body[0, 4].unpack1('L<')
45
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_RES_PQ
46
+
47
+ offset = 4
48
+
49
+ nonce = @body[offset, 16]
50
+ offset += 16
51
+
52
+ server_nonce = @body[offset, 16]
53
+ offset += 16
54
+
55
+ pq_length_byte = @body[offset].unpack1('C')
56
+ offset += 1
57
+
58
+ pq_length = if pq_length_byte == 254
59
+ @body[offset, 3].unpack1('L<') & 0xffffff
60
+ offset += 3
61
+ else
62
+ pq_length_byte
63
+ end
64
+
65
+ pq = @body[offset, pq_length]
66
+ offset += pq_length
67
+ offset += padding_length(pq_length + 1)
68
+
69
+ vector_constructor = @body[offset, 4].unpack1('L<')
70
+ offset += 4
71
+ raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
72
+
73
+ fingerprints_count = @body[offset, 4].unpack1('L<')
74
+ offset += 4
75
+
76
+ fingerprints = []
77
+ fingerprints_count.times do
78
+ fingerprints << @body[offset, 8].unpack1('Q<')
79
+ offset += 8
80
+ end
81
+
82
+ {
83
+ nonce: nonce,
84
+ server_nonce: server_nonce,
85
+ pq: pq,
86
+ fingerprints: fingerprints
87
+ }
88
+ end
89
+
90
+ private
91
+
92
+ def generate_msg_id
93
+ time_ns = (Time.now.to_f * 1_000_000_000).to_i
94
+ (time_ns / 4) * 4
95
+ end
96
+
97
+ def padding_length(length)
98
+ (4 - (length % 4)) % 4
99
+ end
100
+
101
+ def self.padding_length(length)
102
+ (4 - (length % 4)) % 4
103
+ end
104
+ end
105
+ end
106
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- VERSION = '0.0.3'
4
+ VERSION = '0.0.4'
5
5
  end
data/lib/mtproto.rb CHANGED
@@ -3,6 +3,9 @@
3
3
  require_relative 'mtproto/version'
4
4
  require_relative 'mtproto/transport/abridged_packet_codec'
5
5
  require_relative 'mtproto/transport/tcp_connection'
6
+ require_relative 'mtproto/tl/message'
7
+ require_relative 'mtproto/crypto/rsa_key'
8
+ require_relative 'mtproto/client'
6
9
 
7
10
  module MTProto
8
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mtproto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artem Levenkov
@@ -20,6 +20,9 @@ files:
20
20
  - ".ruby-version"
21
21
  - Rakefile
22
22
  - lib/mtproto.rb
23
+ - lib/mtproto/client.rb
24
+ - lib/mtproto/crypto/rsa_key.rb
25
+ - lib/mtproto/tl/message.rb
23
26
  - lib/mtproto/transport/abridged_packet_codec.rb
24
27
  - lib/mtproto/transport/tcp_connection.rb
25
28
  - lib/mtproto/version.rb