lib/shadowsocks_ruby/protocols/obfs/tls_ticket.rb
require 'lrucache'
module ShadowsocksRuby
module Protocols
# TLS 1.2 Obfuscation Protocol
#
# Specification:
# * https://github.com/shadowsocksr/shadowsocksr/blob/manyuser/shadowsocks/obfsplugin/obfs_tls.py
# * https://github.com/shadowsocksr/obfsplugin/blob/master/c/tls1.2_ticket.c
#
# TLS 1.2 Reference:
# * https://en.wikipedia.org/wiki/Transport_Layer_Security
# * https://tools.ietf.org/html/rfc5246
# * https://tools.ietf.org/html/rfc5077
# * https://tools.ietf.org/html/rfc6066
class TlsTicketProtocol
include DummyHelper
include BufferHelper
VERSION_SSL_3_0 = [3, 0]
VERSION_TLS_1_0 = [3, 1]
VERSION_TLS_1_1 = [3, 2]
VERSION_TLS_1_2 = [3, 3]
#Content types
CTYPE_ChangeCipherSpec = 0x14
CTYPE_Alert = 0x15
CTYPE_Handshake = 0x16
CTYPE_Application = 0x17
CTYPE_Heartbeat = 0x18
#Message types
MTYPE_HelloRequest = 0
MTYPE_ClientHello = 1
MTYPE_ServerHello = 2
MTYPE_NewSessionTicket = 4
MTYPE_Certificate = 11
MTYPE_ServerKeyExchange = 12
MTYPE_CertificateRequest = 13
MTYPE_ServerHelloDone = 14
MTYPE_CertificateVerify = 15
MTYPE_ClientKeyExchange = 16
MTYPE_Finished = 20
attr_accessor :next_protocol
# @param [Hash] params Configuration parameters
# @option params [String] :host shadowsocks server address, required by remoteserver protocol
# @option params [String] :key key, required by both remoteserver and localbackend protocol
# @option params [Boolean] :compatible compatibility with origin mode, default _true_
# @option params [String] :obfs_param obfs param, optional
# @option params [LRUCache] :lrucache lrucache, optional, it intened to be a lrucache Proxy if provided
def initialize params = {}
@params = {:compatible => true}.merge(params)
@buffer = ''
@client_id = Random.new.bytes(32)
@max_time_dif = 60 * 60 * 24 # time dif (second) setting
@startup_time = Time.now.to_i - 60 * 30
@client_data = @params[:lrucache] || LRUCache.new(:ttl => 60 * 5)
@connected = EM::DefaultDeferrable.new
end
def tcp_send_to_remoteserver_first_packet data
@connected.callback { send_client_change_cipherspec_and_finish data }
class << self
alias tcp_send_to_remoteserver tcp_send_to_remoteserver_other_packet
end
end
alias tcp_send_to_remoteserver tcp_send_to_remoteserver_first_packet
# TLS 1.2 Application Pharse
def tcp_send_to_remoteserver_other_packet data
@connected.callback { send_data_application_pharse data }
end
def tcp_receive_from_remoteserver_first_packet n
send_client_hello
recv_server_hello
@connected.succeed
class << self
alias tcp_receive_from_remoteserver tcp_receive_from_remoteserver_other_packet
end
tcp_receive_from_remoteserver_other_packet n
end
alias tcp_receive_from_remoteserver tcp_receive_from_remoteserver_first_packet
# TLS 1.2 Application Pharse
def tcp_receive_from_remoteserver_other_packet n
head = async_recv 3
if head != [CTYPE_Application, *VERSION_TLS_1_2].pack("C3")
raise PharseError, "client_decode appdata error"
end
size = async_recv(2).unpack("n")[0]
@buffer << async_recv(size)
tcp_receive_from_remoteserver_other_packet_helper n
end
def tcp_receive_from_localbackend_first_packet n
class << self
alias tcp_receive_from_localbackend tcp_receive_from_localbackend_other_packet
end
recv_client_hello
@no_effect ||= nil
if !@no_effect
send_server_hello
recv_client_change_cipherspec_and_finish
tcp_receive_from_localbackend_other_packet n
else
tcp_receive_from_localbackend_other_packet_helper n
end
end
alias tcp_receive_from_localbackend tcp_receive_from_localbackend_first_packet
# TLS 1.2 Application Pharse
def tcp_receive_from_localbackend_other_packet n
@no_effect ||= nil
if !@no_effect
begin
head = async_recv 3
if head != [CTYPE_Application, *VERSION_TLS_1_2].pack("C3")
raise PharseError, "server_decode appdata error"
end
size = async_recv(2).unpack("n")[0]
end while size == 0
@buffer << async_recv(size)
end
tcp_receive_from_localbackend_other_packet_helper n
end
def tcp_send_to_localbackend data
@no_effect ||= nil
if !@no_effect
send_data_application_pharse data
else
send_data data
end
end
# helpers
def get_random
verifyid = [Time.now.to_i].pack("N") << Random.new.bytes(18)
hello = ""
hello << verifyid # Random part 1
hello << ShadowsocksRuby::Cipher.hmac_sha1_digest(@params[:key] + @client_id, verifyid) # Random part 2
end
def send_client_hello
client_hello = ""
client_hello << [*VERSION_TLS_1_2].pack("C2") # ProtocolVersion
client_hello << get_random # Random len 32
client_hello << [32].pack("C") << @client_id # SessionID
client_hello << Util.hex2bin("001cc02bc02fcca9cca8cc14cc13c00ac014c009c013009c0035002f000a") # CipherSuite
client_hello << Util.hex2bin("0100") # CompressionMethod
ext = Util.hex2bin("ff01000100") # Extension 1 (type ff01 + len 0001 + data 00 )
hosts = @params[:obfs_param] || @params[:host]
if (hosts == nil or hosts == "")
raise PharseError, "No :host or :obfs_param parameters"
end
if (("0".."9").include? hosts[-1])
hosts = ""
end
hosts = hosts.split(",")
if hosts.length != 0
host = hosts[Random.rand(hosts.length)]
else
host = ""
end
ext << make_ext_sni(host) # Extension 2
ext << Util.hex2bin("00170000") # Extension 3 (type 0017 + len 0000)
ext << Util.hex2bin("002300d0") << Random.new.bytes(208) # ticket, Extension 4 (type 0023 + len 00d0 + data)
ext << Util.hex2bin("000d001600140601060305010503040104030301030302010203") # Extension 5 (type 000d + len 0016 + data)
ext << Util.hex2bin("000500050100000000") # Extension 6 (type 0005 + len 0005 + data)
ext << Util.hex2bin("00120000") # Extension 7 (type 0012 + len 0000)
ext << Util.hex2bin("75500000") # Extension 8 (type 7550 + len 0000)
ext << Util.hex2bin("000b00020100") # Extension 9 (type 000b + len 0002 + data)
ext << Util.hex2bin("000a0006000400170018") # Extension 10 (type 000a + len 0006 + data)
client_hello << [ext.length].pack("n") << ext # Extension List
client_handshake_message = [MTYPE_ClientHello, 0, client_hello.length].pack("CCn") << client_hello
handshake_message = [CTYPE_Handshake,*VERSION_TLS_1_0, client_handshake_message.length].pack("C3n") << client_handshake_message
send_data(handshake_message)
end
def send_client_change_cipherspec_and_finish data
buf = ""
buf << [CTYPE_ChangeCipherSpec, *VERSION_TLS_1_2, 0, 1, 1].pack("C*")
buf << [CTYPE_Handshake, *VERSION_TLS_1_2, 32].pack("C3n") << Random.new.bytes(22)
buf << ShadowsocksRuby::Cipher::hmac_sha1_digest(@params[:key] + @client_id, buf)
buf << [CTYPE_Application, *VERSION_TLS_1_2, data.length].pack("C3n") << data
send_data buf
end
def recv_server_hello
data = async_recv(129) # ServerHello 76 + ServerChangeSipherSpec 6 + Finished 37
verify = data[11 ... 33]
if ShadowsocksRuby::Cipher.hmac_sha1_digest(@params[:key] + @client_id, verify) != data[33 ... 43]
raise PharseError, "client_decode data error"
end
end
def recv_client_hello
data = async_recv 3
if data != [CTYPE_Handshake, *VERSION_TLS_1_0].pack("C3")
if @params[:compatible]
@buffer = data
@no_effect = true
return
else
raise PharseError, "decode error"
end
end
len_client_handshake_message = async_recv(2).unpack("n")[0]
client_handshake_message = async_recv(len_client_handshake_message)
if (client_handshake_message.slice!(0, 2) != [MTYPE_ClientHello, 0].pack("C2"))
raise PharseError, "tls_auth not client hello message"
end
len_client_hello = client_handshake_message.slice!(0, 2).unpack("n")[0]
client_hello = client_handshake_message
if (len_client_hello != client_hello.length )
raise PharseError, "tls_auth wrong message size"
end
if (client_hello.slice!(0,2) != [*VERSION_TLS_1_2].pack("C2"))
raise PharseError, "tls_auth wrong tls version"
end
verifyid = client_hello.slice!(0, 32)
len_sessionid = client_hello.slice!(0,1).unpack("C")[0]
if (len_sessionid < 32)
raise PharseError, "tls_auth wrong sessionid_len"
end
sessionid = client_hello.slice!(0, len_sessionid)
@client_id = sessionid
sha1 = ShadowsocksRuby::Cipher::hmac_sha1_digest(@params[:key] + sessionid, verifyid[0, 22])
utc_time = Time.at(verifyid[0, 4].unpack("N")[0])
time_dif = Time.now.to_i - utc_time.to_i
#if @params[:obfs_param] != nil
# @max_time_dif = @params[:obfs_param].to_i
#end
if @max_time_dif > 0 && (time_dif.abs > @max_time_dif or utc_time.to_i - @startup_time < - @max_time_dif / 2)
raise PharseError, "tls_auth wrong time"
end
if sha1 != verifyid[22 .. -1]
raise PharseError, "tls_auth wrong sha1"
end
if @client_data[verifyid[0, 22]]
raise PharseError, "replay attack detect, id = #{Util.bin2hex(verifyid)}"
end
@client_data[verifyid[0, 22]] = sessionid
end
def send_server_hello
data = [*VERSION_TLS_1_2].pack("C2")
data << get_random
data << Util.hex2bin("20") # len 32 in decimal
data << @client_id
data << Util.hex2bin("c02f000005ff01000100")
data = Util.hex2bin("0200") << [data.length].pack("n") << data
data = Util.hex2bin("160303") << [data.length].pack("n") << data # ServerHello len 86 (11 + 32 + 1 + 32 + 10)
data << Util.hex2bin("14") << [*VERSION_TLS_1_2].pack("C2") << Util.hex2bin("000101") # ChangeCipherSpec len (6)
data << Util.hex2bin("16") << [*VERSION_TLS_1_2].pack("C2") << Util.hex2bin("0020") << Random.new.bytes(22)
data << ShadowsocksRuby::Cipher.hmac_sha1_digest(@params[:key] + @client_id, data) # Finished len(37)
send_data data # len 129
end
def recv_client_change_cipherspec_and_finish
data = async_recv 43
if data[0, 6] != [CTYPE_ChangeCipherSpec, *VERSION_TLS_1_2, 0, 1, 1].pack("C*") # ChangeCipherSpec
raise PharseError, "server_decode data error"
end
if data[6, 5] != [CTYPE_Handshake, *VERSION_TLS_1_2, 32].pack("C3n") # Finished
raise PharseError, "server_decode data error"
end
if ShadowsocksRuby::Cipher.hmac_sha1_digest(@params[:key] + @client_id, data[0, 33]) != data[33, 10]
raise PharseError, "server_decode data error"
end
end
def send_data_application_pharse data
buf = ""
while data.length > 2048
size = [Random.rand(65535) % 4096 + 100, data.length].min
buf << [CTYPE_Application, *VERSION_TLS_1_2, size].pack("C3n") << data.slice!(0, size)
end
if data.length > 0
buf << [CTYPE_Application, *VERSION_TLS_1_2, data.length].pack("C3n") << data
end
send_data buf
end
def make_ext_sni host
name_type = 0 #host_name
server_name = [name_type, host.length].pack("Cn") << host
server_name_list = [server_name.length].pack("n") << server_name
type = Util.hex2bin("0000")
data = [server_name_list.length].pack("n") << server_name_list
return type << data
end
alias tcp_receive_from_client raise_me
alias tcp_send_to_client raise_me
#alias tcp_receive_from_remoteserver raise_me
#alias tcp_send_to_remoteserver raise_me
#alias tcp_receive_from_localbackend raise_me
#alias tcp_send_to_localbackend raise_me
alias tcp_receive_from_destination raise_me
alias tcp_send_to_destination raise_me
alias udp_receive_from_client raise_me
alias udp_send_to_client raise_me
alias udp_receive_from_remoteserver raise_me
alias udp_send_to_remoteserver raise_me
alias udp_receive_from_localbackend raise_me
alias udp_send_to_localbackend raise_me
alias udp_receive_from_destination raise_me
alias udp_send_to_destination raise_me
end
end
end