beam.rb 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. # Copyright 2022 Marek Küthe
  2. # GNU GPLv3
  3. # This program can transfer a file from one device to another.
  4. # For this it uses two tunnel, a backup tunnel each of a length of one hop.
  5. # This can be changed in the source code.
  6. # Requires gems: base32, base_x
  7. require "base64"
  8. require "base32"
  9. require "base_x"
  10. require "openssl"
  11. require "securerandom"
  12. require_relative "samapi.rb"
  13. $chunk_size = 4 * 1024
  14. def set_status status
  15. if status[0]
  16. puts "Status = OK"
  17. else
  18. puts "Error = #{status[-1][:args].values.join " "}"
  19. puts "About!"
  20. exit!
  21. end
  22. end
  23. def privkey_to_hash priv_key
  24. priv_key = priv_key.tr "-~", "+/"
  25. # conversion of base64 alphabet from i2p to standard
  26. priv_bin = Base64.decode64 priv_key
  27. # decode the private key
  28. cert_len = priv_bin[385...387].unpack("S>")[0]
  29. # determining the length of the certificate, others are constants for this case
  30. pub_len = 256 + 128 + 1 + 2 + cert_len
  31. # see https://geti2p.net/spec/common-structures#keysandcert
  32. # calculation of the length of the public part / destination
  33. hash = OpenSSL::Digest::SHA256.digest priv_bin[0...pub_len]
  34. return hash
  35. end
  36. def encode_hash hash, passwd = "\0\0\0\0\0"
  37. passwd.length == 5 or raise ArgumentError.new("Passwd must have length 5")
  38. bin = passwd + hash
  39. checksum = bin.bytes.reduce(0, :+) % 10
  40. checksum_bin = [checksum].pack("C")
  41. bin = checksum_bin + bin
  42. b36 = BaseX.encode(bin, numerals: "0123456789abcdefghijkmnopqrstuvwxyz").chars
  43. return b36.each_slice(4).map(&:join)
  44. end
  45. def decode_hash codes
  46. bin = BaseX.decode(codes, numerals: "0123456789abcdefghijkmnopqrstuvwxyz")
  47. checksum_bin = bin[0]
  48. checksum = checksum_bin.unpack("C")[0]
  49. passwd = bin[1...6]
  50. hash = bin[6..-1]
  51. calc_checksum = bin[1..-1].bytes.reduce(0, :+) % 10
  52. return [checksum == calc_checksum, passwd, hash]
  53. end
  54. def hash_to_b32 hash
  55. return Base32.encode(hash).tr("=", "").downcase + ".b32.i2p"
  56. end
  57. def display_transfercode code
  58. tf = (code.map { |code_block|
  59. code_block.upcase
  60. }).join " "
  61. puts "TRANSFER CODE: TF #{tf}"
  62. end
  63. if ARGV[0]
  64. id = "BeamBotSender#{Random.rand 100}"
  65. if ! File.readable? ARGV[0]
  66. puts "Error = File to send unreadable."
  67. exit!
  68. end
  69. puts "Task = Calculating checksum for file"
  70. file_checksum = OpenSSL::Digest::MD5.file(ARGV[0]).hexdigest
  71. puts "Task = Preparing i2p session"
  72. control = SamApi.new
  73. set_status control.handshake
  74. comm = SamApi.new
  75. set_status comm.handshake
  76. passwd = SecureRandom.random_bytes(5)
  77. puts "Task = Preparing serving of file"
  78. server = TCPServer.new "127.0.0.1", 0
  79. Thread.new do
  80. loop {
  81. Thread.new(server.accept) { |socket|
  82. header = socket.gets.chomp.split " "
  83. b32 = hash_to_b32 privkey_to_hash header[0]
  84. puts "Request from #{b32}"
  85. in_passwd = socket.read 5
  86. if in_passwd == passwd
  87. socket.puts File.basename ARGV[0]
  88. socket.puts file_checksum
  89. puts "#{b32} has successfully authenticated"
  90. fil = File.open ARGV[0], "rb"
  91. while ! fil.eof?
  92. socket.write fil.read $chunk_size
  93. end
  94. fil.close
  95. else
  96. puts "WARNING - #{b32} did not authenticate successfully."
  97. end
  98. socket.close
  99. }
  100. }
  101. end
  102. host = server.addr[3]
  103. port = server.addr[1]
  104. puts "Task = Preparing i2p session"
  105. res = control.session_create(
  106. "STYLE" => "STREAM",
  107. "ID" => id,
  108. "DESTINATION" => "TRANSIENT",
  109. "SIGNATURE_TYPE" => "EdDSA_SHA512_Ed25519",
  110. "inbound.length" => "1",
  111. "outbound.length" => "1",
  112. "inbound.quantity" => "2",
  113. "outbound.quantity" => "2",
  114. "inbound.backupQuantity" => "1",
  115. "outbound.backupQuantity" => "1"
  116. )
  117. priv_key = res[1]
  118. set_status res
  119. set_status comm.stream_forward(
  120. "ID" => id,
  121. "PORT" => port,
  122. "HOST" => host,
  123. "SILENT" => false
  124. )
  125. hash = privkey_to_hash priv_key
  126. transfercode = encode_hash hash, passwd
  127. display_transfercode transfercode
  128. loop {
  129. control.check_ping
  130. comm.check_ping
  131. status = control.send_ping and comm.send_ping
  132. if ! status
  133. puts "Warning = No ping"
  134. end
  135. sleep 10
  136. }
  137. else
  138. id = "BeamBotRecipent#{Random.rand 100}"
  139. puts "Task = Preparing i2p session"
  140. control = SamApi.new
  141. set_status control.handshake
  142. puts "Task = Preparing i2p session"
  143. res = control.session_create(
  144. "STYLE" => "STREAM",
  145. "ID" => id,
  146. "DESTINATION" => "TRANSIENT",
  147. "SIGNATURE_TYPE" => "EdDSA_SHA512_Ed25519",
  148. "inbound.length" => "1",
  149. "outbound.length" => "1",
  150. "inbound.quantity" => "2",
  151. "outbound.quantity" => "2",
  152. "inbound.backupQuantity" => "1",
  153. "outbound.backupQuantity" => "1"
  154. )
  155. set_status res
  156. print "Transfer code: TF "
  157. STDOUT.flush
  158. tf = gets.chomp
  159. tf = tf.tr(" ", "").downcase.delete_prefix("tf")
  160. res = decode_hash tf
  161. if ! res[0]
  162. puts "Warning = Mismatch checksum"
  163. end
  164. passwd = res[1]
  165. hash = res[2]
  166. b32 = hash_to_b32 hash
  167. puts "Task = Determine exact destination"
  168. lookup_result = control.naming_lookup b32
  169. set_status lookup_result
  170. b64 = lookup_result[1]
  171. control.check_ping
  172. puts "Task = Connect to destination"
  173. comm = SamApi.new
  174. set_status comm.handshake
  175. stream = comm.stream_connect(
  176. "ID" => id,
  177. "DESTINATION" => b64
  178. )
  179. set_status stream
  180. puts "Info = Connected"
  181. socket = stream[1]
  182. puts "Task = Authenticating"
  183. socket.write passwd
  184. socket.flush
  185. if socket.closed? or socket.eof?
  186. puts "Info = Authentication failed"
  187. else
  188. puts "Info = Authentication successful"
  189. puts "Task = Receive file"
  190. filename = socket.gets.chomp
  191. file_checksum = socket.gets.chomp
  192. filename = "recived_#{filename}"
  193. fil = File.new filename, "wb"
  194. print "Task = Receive chunks"
  195. STDOUT.flush
  196. while ! socket.eof?
  197. fil.write socket.read $chunk_size
  198. print "."
  199. STDOUT.flush
  200. end
  201. fil.close
  202. puts
  203. puts "Task = Checking the file for corruption"
  204. calc_checksum = OpenSSL::Digest::MD5.file(filename).hexdigest
  205. if calc_checksum == file_checksum
  206. puts "Info = The file was received successfully"
  207. else
  208. puts "Warning = The file was received corrupted"
  209. end
  210. puts
  211. end
  212. end