Last active
September 26, 2023 09:58
-
-
Save unasuke/b30a4716248b1831bd428af3b7829ce7 to your computer and use it in GitHub Desktop.
Parse QUIC Initial Packet by Ruby
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This software is released under the MIT License. | |
# https://opensource.org/licenses/MIT | |
require 'bundler/inline' | |
gemfile do | |
source "https://rubygems.org" | |
gem "bindata" | |
end | |
require 'openssl' | |
require 'bindata' | |
def tms(bit) | |
case bit | |
when 0 then 6 | |
when 1 then 14 | |
when 2 then 30 | |
when 3 then 62 | |
end | |
end | |
class QUICInitialPacket < BinData::Record | |
endian :big | |
bit1 :header_form, asserted_value: 1 | |
bit1 :fixed_bit, asserted_value: 1 | |
bit2 :long_packet_type, asserted_value: 0 | |
bit2 :reserved_bit | |
bit2 :packet_number_length | |
bit32 :version | |
bit8 :destination_connection_id_length | |
bit :destination_connection_id, nbits: lambda { destination_connection_id_length * 8 } | |
bit8 :source_connection_id_length | |
bit :source_connection_id, nbits: lambda { source_connection_id_length * 8 } | |
bit2 :token_two_most_significant_bits | |
bit :token_length, nbits: lambda { tms(token_two_most_significant_bits) } | |
string :token, read_length: lambda { token_length } | |
bit2 :length_two_most_significant_bits | |
bit :length_length, nbits: lambda { tms(length_two_most_significant_bits) } | |
bit :packet_number, nbits: lambda { (packet_number_length + 1) * 8 } | |
string :payload, read_length: lambda { length_length - (packet_number_length + 1) } | |
end | |
class QUICProtectedInitialPacket < BinData::Record | |
endian :big | |
bit1 :header_form, asserted_value: 1 | |
bit1 :fixed_bit, asserted_value: 1 | |
bit2 :long_packet_type, asserted_value: 0 | |
bit2 :reserved_bit | |
bit2 :packet_number_length # unuse (protected value) | |
bit32 :version | |
bit8 :destination_connection_id_length | |
bit :destination_connection_id, nbits: lambda { destination_connection_id_length * 8 } | |
bit8 :source_connection_id_length | |
bit :source_connection_id, nbits: lambda { source_connection_id_length * 8 } | |
# Variable-Length Integer Encoding for token | |
bit2 :token_two_most_significant_bits | |
bit :token_length, nbits: lambda { tms(token_two_most_significant_bits) } | |
string :token, read_length: lambda { token_length } | |
# Variable-Length Integer Encoding for length | |
bit2 :length_two_most_significant_bits | |
bit :length_length, nbits: lambda { tms(length_two_most_significant_bits) } | |
string :payload, read_length: lambda { length_length - (packet_number_length + 1) } | |
end | |
class QUICCRYPTOFrame < BinData::Record | |
endian :big | |
bit8 :frame_type, asserted_value: 0x06 | |
bit2 :offset_two_most_significat_bits | |
bit :offset, nbits: lambda { tms(offset_two_most_significat_bits) } | |
bit2 :length_two_most_significant_bits | |
bit :length_length, nbits: lambda { tms(length_two_most_significant_bits) } | |
string :data, read_length: lambda { length_length } | |
end | |
# https://www.rfc-editor.org/rfc/rfc9001#section-a.2-7 | |
raw_packet = [ | |
"c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11" + | |
"d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f399" + | |
"1c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c" + | |
"8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df6212" + | |
"30c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5" + | |
"457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c208" + | |
"4dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec" + | |
"4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3" + | |
"485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db" + | |
"059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c" + | |
"7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f8" + | |
"9937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556" + | |
"be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c74" + | |
"68449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663a" + | |
"c69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00" + | |
"f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632" + | |
"291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe58964" + | |
"25c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd" + | |
"14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ff" + | |
"ef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198" + | |
"e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009dd" + | |
"c324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73" + | |
"203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77f" + | |
"cb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450e" + | |
"fc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03ade" + | |
"a2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e724047" + | |
"90a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2" + | |
"162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f4" + | |
"40591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca0" + | |
"6948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e" + | |
"8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0" + | |
"be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f09400" + | |
"54da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab" + | |
"760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9" + | |
"f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4" + | |
"056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd4684064" + | |
"7e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241" + | |
"e221af44860018ab0856972e194cd934" | |
].pack("H*") | |
packet = QUICProtectedInitialPacket.read(raw_packet) | |
# ここからheaderの保護を解除するためのコード | |
pn_offset = 7 + | |
packet.destination_connection_id_length + | |
packet.source_connection_id_length + | |
(tms(packet.length_two_most_significant_bits) + 2) / 8 + | |
(tms(packet.token_two_most_significant_bits) + 2) / 8 + | |
packet.token_length | |
sample_offset = pn_offset + 4 | |
sample = raw_packet[sample_offset...sample_offset+16] | |
enc = OpenSSL::Cipher.new('aes-128-ecb') | |
enc.encrypt | |
enc.key = ["9f50449e04a0e810283a1e9933adedd2"].pack("H*") # hp | |
mask = "" | |
mask << enc.update(sample) | |
mask << enc.final | |
# https://www.rfc-editor.org/rfc/rfc9001#name-header-protection-applicati | |
# headerを保護するときとは逆の手順を踏んで保護を解除する | |
raw_packet[0] = [(raw_packet[0].unpack1('H*').to_i(16) ^ (mask[0].unpack1('H*').to_i(16) & 0x0f)).to_s(16)].pack("H*") | |
# https://www.rfc-editor.org/rfc/rfc9001#figure-6 | |
pn_length = (raw_packet[0].unpack1('H*').to_i(16) & 0x03) + 1 | |
packet_number = | |
(raw_packet[pn_offset...pn_offset+pn_length].unpack1("H*").to_i(16) ^ mask[1...1+pn_length].unpack1("H*").to_i(16)).to_s(16) | |
# 先頭の0が消えてしまうので、パケット番号の長さに満たないぶんを zero fillする | |
raw_packet[pn_offset...pn_offset+pn_length] = [("0" * (pn_length * 2 - packet_number.length)) + packet_number].pack("H*") | |
# headerの保護が外れたpacket (payloadはまだ暗号) | |
packet = QUICInitialPacket.read(raw_packet) | |
# 復号のためheaderのみを取り出す | |
header_length = raw_packet.length - packet.payload.length | |
# payloadの復号 | |
dec = OpenSSL::Cipher.new('aes-128-gcm') | |
dec.decrypt | |
dec.key = ["1f369613dd76d5467730efcbe3b1a22d"].pack("H*") # quic key | |
dec.iv = [("fa044b2f42a3fd3b46fb255c".to_i(16) ^ packet.packet_number).to_s(16)].pack("H*") # quic iv | |
dec.auth_data = raw_packet[0...(raw_packet.length - packet.payload.length)] | |
dec.auth_tag = packet.payload[packet.payload.length-16...packet.payload.length] | |
payload = "" | |
payload << dec.update(packet.payload[0...packet.payload.length-16]) | |
payload << dec.final | |
# 復号したpayloadをCRYPTO frameとしてparse | |
pp QUICCRYPTOFrame.read(payload) | |
# => {:frame_type=>6, | |
# :offset_two_most_significat_bits=>0, | |
# :offset=>0, | |
# :length_two_most_significant_bits=>1, | |
# :length_length=>241, | |
# :data=> | |
# "\x01\x00\x00\xED\x03\x03\xEB\xF8\xFAV\xF1)9\xB9XJ8\x96G.\xC4\v\xB8c\xCF\xD3\xE8h\x04\xFE:G\xF0j+iHL\x00\x00\x04\x13\x01\x13\x02\x01\x00\x00\xC0" + | |
# "\x00\x00\x00\x10\x00\x0E\x00\x00\vexample.com\xFF\x01\x00\x01\x00\x00\n" + | |
# "\x00\b\x00\x06\x00\x1D\x00\x17\x00\x18\x00\x10\x00\a\x00\x05\x04alpn\x00\x05\x00\x05\x01\x00\x00\x00\x00\x003\x00&\x00$\x00\x1D\x00 \x93p\xB2\xC9\xCA\xA4" + | |
# "\x7F\xBA\xBA\xF4U\x9F\xED\xBAu=\xE1q\xFAq\xF5\x0F\x1C\xE1]C\xE9\x94\xECt\xD7H\x00+\x00\x03\x02\x03\x04\x00\r\x00\x10\x00\x0E\x04\x03\x05\x03\x06\x03\x02\x03" + | |
# "\b\x04\b\x05\b\x06\x00-\x00\x02\x01\x01\x00\x1C\x00\x02@\x01\x009\x002\x04\b\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x05\x04\x80\x00\xFF\xFF\a\x04\x80\x00\xFF\xFF\b" + | |
# "\x01\x10\x01\x04\x80\x00u0\t\x01\x10\x0F\b\x83\x94\xC8\xF0>QW\b\x06\x04\x80\x00\xFF\xFF" | |
# } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment