Skip to content

Instantly share code, notes, and snippets.

@orisano
Last active August 17, 2021 12:56
Show Gist options
  • Save orisano/0efc65f96b81ca7c174fedd3431de611 to your computer and use it in GitHub Desktop.
Save orisano/0efc65f96b81ca7c174fedd3431de611 to your computer and use it in GitHub Desktop.
unasuke quic
# This software is released under the MIT License.
# Copyright 2021 Yusuke Nakamura
# Copyright 2021 Nao Yonashiro
# https://opensource.org/licenses/MIT
require "bundler/inline"
require "socket"
gemfile do
source "https://rubygems.org"
gem "bindata"
gem "tttls1.3"
end
require "tttls1.3/key_schedule"
require "openssl"
def xor_s(a, b)
a.unpack("C*").zip(b.unpack("C*")).map{|x, y| x ^ y}.pack("C*")
end
class VarInt < BinData::Primitive
bit2 :bytes_log2
bit :val, :nbits => lambda { bytes * 8 - 2 }
def get
self.val
end
def set(v)
self.val = v
end
def bytes
2 ** self.bytes_log2
end
end
class QuicLongHeader < BinData::Record
hide :header_form, :fixed_bit
bit1 :header_form, :asserted_value => 1
bit1 :fixed_bit, :asserted_value => 1
bit2 :long_packet_type
bit4 :type_specific_bits
bit32 :version
bit8 :destination_connection_id_length
string :destination_connection_id, :length => :destination_connection_id_length
bit8 :source_connection_id_length
string :source_connection_id, :length => :source_connection_id_length
def bytes
1 + 4 + 1 + destination_connection_id.length + 1 + source_connection_id.length
end
end
class QuicKeys
def initialize(data)
initial_salt = ["38762cf7f55934b34d179ae6a4c80cadccbb7f0a"].pack("H*")
initial_secret = OpenSSL::HMAC.digest("SHA256", initial_salt, data)
@client_initial_secret = TTTLS13::KeySchedule.hkdf_expand_label(initial_secret, "client in", "", 32, "SHA256")
end
def hp
TTTLS13::KeySchedule::hkdf_expand_label(@client_initial_secret, "quic hp", "", 16, "SHA256")
end
def iv
TTTLS13::KeySchedule::hkdf_expand_label(@client_initial_secret, "quic iv", "", 12, "SHA256")
end
def key
TTTLS13::KeySchedule::hkdf_expand_label(@client_initial_secret, "quic key", "", 16, "SHA256")
end
end
class QuicInitialPacketHeader < BinData::Record
default_parameter :unprotected => false
quic_long_header :long_header
var_int :token_length
string :token, :length => :token_length
var_int :len
string :packet_number, :read_length => :packet_number_length
virtual :unprotected, :inital_value => :unprotected
def remove_protection(raw_packet)
pn_offset = long_header.bytes + token_length.bytes + token.length + len.bytes
sample_offset = pn_offset + 4
sample = raw_packet[sample_offset...sample_offset+16]
keys = QuicKeys.new(long_header.destination_connection_id)
enc = OpenSSL::Cipher.new("aes-128-ecb").encrypt
enc.key = keys.hp
mask = enc.update(sample) + enc.final
raw_packet[0] = [raw_packet.unpack1("C") ^ (mask.unpack1("C") & 0x0f)].pack("C")
pn_length = (raw_packet.unpack1("C") & 0x03) + 1
raw_packet[pn_offset...pn_offset+pn_length] = xor_s(raw_packet[pn_offset...pn_offset+pn_length], mask[1...1+pn_length])
end
def packet_number_length
if unprotected
(long_header.type_specific_bits & 0x03) + 1
else
0
end
end
end
class QuicInitialPacket < BinData::Record
quic_initial_packet_header :header, :unprotected => true
string :payload, :read_length => lambda { header.len - header.packet_number_length }
def decrypt!
keys = QuicKeys.new(header.long_header.destination_connection_id)
dec = OpenSSL::Cipher.new("aes-128-gcm").decrypt
dec.key = keys.key
dec.iv = xor_s(keys.iv, header.packet_number.rjust(12, "\x00"))
dec.auth_data = header.to_binary_s
dec.auth_tag = self.payload[-16...]
self.payload = dec.update(self.payload[...-16]) + dec.final
end
end
class QuicPaddingFrame < BinData::Record
end
class QuicPingFrame < BinData::Record
end
class QuicCryptoFrame < BinData::Record
var_int :offset
var_int :len
string :data, :read_length => :len
end
class QuicFrame < BinData::Record
bit8 :frame_type
choice :body, :selection => :frame_type do
quic_padding_frame 0x00
quic_ping_frame 0x01
quic_crypto_frame 0x06
end
end
class QuicFrames < BinData::Record
array :frames, :type => :quic_frame, :read_until => :eof
end
class TlsProtocolVersion < BinData::Record
bit8 :major
bit8 :minor
end
class TlsRandom < BinData::Record
bit32 :gmt_unix_time
string :random_bytes, :length => 28
end
class TlsServerNameExtension < BinData::Record
bit16 :len
buffer :server_name_list, :length => :len do
array :read_until => :eof do
bit8 :name_type, :asserted_value => 0
bit16 :name_length
string :name, :read_length => :name_length
end
end
end
class TlsSupportedGroupsExtension < BinData::Record
bit16 :len
buffer :named_group_list, :length => :len do
array :type => :bit16, :read_until => :eof
end
end
class TlsApplicationLayerProtocolNegotiationExtension < BinData::Record
bit16 :len
buffer :protocol_name_list, :length => :len do
array :read_until => :eof do
bit8 :len
string :name, :length => :len
end
end
end
class TlsSignatureAlgorithmsExtension < BinData::Record
bit16 :len
buffer :supported_signature_algorithms, :length => :len do
array :type => :bit16, :read_until => :eof
end
end
class TlsKeyShareExtension < BinData::Record
bit16 :len
buffer :client_shares, :length => :len do
array :read_until => :eof do
bit16 :group
bit16 :key_exchange_length
string :key_exchange, :length => :key_exchange_length
end
end
end
class TlsPskKeyExchangeModesExtension < BinData::Record
bit8 :len
array :ke_modes, :type => :bit8, :initial_length => :len
end
class TlsSupportedVersionsExtension < BinData::Record
bit8 :len
buffer :versions, :length => :len do
array :type => :tls_protocol_version, :read_until => :eof
end
end
class QuicTransportParametersExtension < BinData::Record
endian :big
array :params, :read_until => :eof do
hide :len
var_int :id
var_int :len
string :val, :read_length => :len
end
end
class TlsExtension < BinData::Record
bit16 :extension_type
bit16 :len
buffer :body, :length => :len do
# https://iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml
choice :selection => :extension_type do
# https://datatracker.ietf.org/doc/html/rfc6066
tls_server_name_extension 0
# https://datatracker.ietf.org/doc/html/rfc8422
tls_supported_groups_extension 10
# https://datatracker.ietf.org/doc/html/rfc7301
tls_application_layer_protocol_negotiation_extension 16
# https://datatracker.ietf.org/doc/html/rfc8446
tls_signature_algorithms_extension 13
tls_key_share_extension 51
tls_psk_key_exchange_modes_extension 45
tls_supported_versions_extension 43
# https://www.rfc-editor.org/rfc/rfc9001.html
# https://www.rfc-editor.org/rfc/rfc9000.html#name-transport-parameter-encodin
quic_transport_parameters_extension 57
end
end
end
class TlsClientHello < BinData::Record
tls_protocol_version :client_version
tls_random :random
bit8 :session_id_length
string :session_id, :length => :session_id_length
bit16 :cipher_suites_length
array :cipher_suites, :type => :bit16, :initial_length => lambda { cipher_suites_length / 2 }
bit8 :compression_methods_length
array :compression_methods, :type => :bit8, :initial_length => :compression_methods_length
bit16 :extensions_length
buffer :extensions, :length => :extensions_length do
array :type => :tls_extension, :read_until => :eof
end
end
class TlsHandshake < BinData::Record
bit8 :msg_type
bit24 :len
buffer :body, :length => :len do
choice :selection => :msg_type do
tls_client_hello 0x01
end
end
end
socket = UDPSocket.new
socket.bind("0.0.0.0", 8080)
loop do
begin
raw_packet, addr = socket.recvfrom_nonblock(2000)
pp addr
rescue IO::WaitReadable
retry
end
QuicInitialPacketHeader.read(raw_packet).remove_protection(raw_packet)
initial_packet = QuicInitialPacket.read(raw_packet)
initial_packet.decrypt!
pp initial_packet
QuicFrames.read(initial_packet.payload).frames.each{|frame|
pp frame
if frame.frame_type == 0x6
pp TlsHandshake.read(frame.body.data)
end
}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment