raioquic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.containerignore +4 -0
- data/.rubocop.yml +93 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Containerfile +6 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +113 -0
- data/LICENSE +28 -0
- data/README.md +48 -0
- data/Rakefile +16 -0
- data/Steepfile +8 -0
- data/example/curlcatcher.rb +18 -0
- data/example/interoperability/README.md +9 -0
- data/example/interoperability/aioquic/aioquic_client.py +47 -0
- data/example/interoperability/aioquic/aioquic_server.py +34 -0
- data/example/interoperability/key.pem +28 -0
- data/example/interoperability/localhost-unasuke-dev.crt +21 -0
- data/example/interoperability/quic-go/sample_server.go +61 -0
- data/example/interoperability/raioquic_client.rb +42 -0
- data/example/interoperability/raioquic_server.rb +43 -0
- data/example/parse_curl_example.rb +108 -0
- data/lib/raioquic/buffer.rb +202 -0
- data/lib/raioquic/core_ext.rb +54 -0
- data/lib/raioquic/crypto/README.md +5 -0
- data/lib/raioquic/crypto/aesgcm.rb +52 -0
- data/lib/raioquic/crypto/backend/aead.rb +52 -0
- data/lib/raioquic/crypto/backend.rb +12 -0
- data/lib/raioquic/crypto.rb +10 -0
- data/lib/raioquic/quic/configuration.rb +81 -0
- data/lib/raioquic/quic/connection.rb +2776 -0
- data/lib/raioquic/quic/crypto.rb +317 -0
- data/lib/raioquic/quic/event.rb +69 -0
- data/lib/raioquic/quic/logger.rb +272 -0
- data/lib/raioquic/quic/packet.rb +471 -0
- data/lib/raioquic/quic/packet_builder.rb +301 -0
- data/lib/raioquic/quic/rangeset.rb +113 -0
- data/lib/raioquic/quic/recovery.rb +528 -0
- data/lib/raioquic/quic/stream.rb +343 -0
- data/lib/raioquic/quic.rb +20 -0
- data/lib/raioquic/tls.rb +1659 -0
- data/lib/raioquic/version.rb +5 -0
- data/lib/raioquic.rb +12 -0
- data/misc/export_x25519.py +43 -0
- data/misc/gen_rfc8448_keypair.rb +90 -0
- data/raioquic.gemspec +37 -0
- data/sig/raioquic/buffer.rbs +37 -0
- data/sig/raioquic/core_ext.rbs +7 -0
- data/sig/raioquic/crypto/aesgcm.rbs +20 -0
- data/sig/raioquic/crypto/backend/aead.rbs +11 -0
- data/sig/raioquic/quic/configuration.rbs +34 -0
- data/sig/raioquic/quic/connection.rbs +277 -0
- data/sig/raioquic/quic/crypto.rbs +88 -0
- data/sig/raioquic/quic/event.rbs +51 -0
- data/sig/raioquic/quic/logger.rbs +57 -0
- data/sig/raioquic/quic/packet.rbs +157 -0
- data/sig/raioquic/quic/packet_builder.rbs +76 -0
- data/sig/raioquic/quic/rangeset.rbs +17 -0
- data/sig/raioquic/quic/recovery.rbs +142 -0
- data/sig/raioquic/quic/stream.rbs +87 -0
- data/sig/raioquic/tls.rbs +444 -0
- data/sig/raioquic.rbs +9 -0
- metadata +121 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require "raioquic"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
HOST = ["0.0.0.0", 4433]
|
5
|
+
|
6
|
+
conf = Raioquic::Quic::QuicConfiguration.new(is_client: true)
|
7
|
+
conf.load_verify_locations(cafile: "localhost-unasuke-dev.crt")
|
8
|
+
client = Raioquic::Quic::Connection::QuicConnection.new(configuration: conf)
|
9
|
+
client.connect(addr: HOST, now: Time.now.to_f)
|
10
|
+
s = UDPSocket.new
|
11
|
+
s.connect("0.0.0.0", 4433)
|
12
|
+
|
13
|
+
# handshake
|
14
|
+
3.times do
|
15
|
+
client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
|
16
|
+
# pp data
|
17
|
+
s.send(data, 0)
|
18
|
+
end
|
19
|
+
data, addr = s.recvfrom(65536)
|
20
|
+
client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
|
21
|
+
# puts("()()()()()()()()()(()()")
|
22
|
+
# pp client.events
|
23
|
+
while ev = client.next_event
|
24
|
+
pp ev
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
stream_id = client.get_next_available_stream_id
|
29
|
+
|
30
|
+
2.times do
|
31
|
+
client.send_stream_data(stream_id: stream_id, data: "hello")
|
32
|
+
client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
|
33
|
+
# pp data
|
34
|
+
s.send(data, 0)
|
35
|
+
end
|
36
|
+
data, addr = s.recvfrom(65536)
|
37
|
+
client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
|
38
|
+
while ev = client.next_event
|
39
|
+
pp ev
|
40
|
+
end
|
41
|
+
end
|
42
|
+
client.close
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "raioquic"
|
2
|
+
require "socket"
|
3
|
+
|
4
|
+
HOST = ["0.0.0.0", 4433]
|
5
|
+
|
6
|
+
conf = Raioquic::Quic::QuicConfiguration.new(is_client: false)
|
7
|
+
conf.load_cert_chain("localhost-unasuke-dev.crt", "key.pem")
|
8
|
+
server = Raioquic::Quic::Connection::QuicConnection.new(configuration: conf)
|
9
|
+
# client.connect(addr: HOST, now: Time.now.to_f)
|
10
|
+
s = UDPSocket.new
|
11
|
+
s.bi
|
12
|
+
s.connect("0.0.0.0", 4433)
|
13
|
+
|
14
|
+
# handshake
|
15
|
+
3.times do
|
16
|
+
client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
|
17
|
+
# pp data
|
18
|
+
s.send(data, 0)
|
19
|
+
end
|
20
|
+
data, addr = s.recvfrom(65536)
|
21
|
+
client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
|
22
|
+
# puts("()()()()()()()()()(()()")
|
23
|
+
# pp client.events
|
24
|
+
while ev = client.next_event
|
25
|
+
pp ev
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
stream_id = client.get_next_available_stream_id
|
30
|
+
|
31
|
+
2.times do
|
32
|
+
client.send_stream_data(stream_id: stream_id, data: "hello")
|
33
|
+
client.datagrams_to_send(now: Time.now.to_f).each do |data, addr|
|
34
|
+
# pp data
|
35
|
+
s.send(data, 0)
|
36
|
+
end
|
37
|
+
data, addr = s.recvfrom(65536)
|
38
|
+
client.receive_datagram(data: data, addr: HOST, now: Time.now.to_f)
|
39
|
+
while ev = client.next_event
|
40
|
+
pp ev
|
41
|
+
end
|
42
|
+
end
|
43
|
+
client.close
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "raioquic"
|
2
|
+
|
3
|
+
# quiche
|
4
|
+
curl_example_1 = [
|
5
|
+
"c400000001101b631c6391a91309c29395aaa5c180bc14db7dafc0e1c10407ff4a9527a71103bb7eb59a91004120487b50905ab8b9b40a27ca5bcbf7" \
|
6
|
+
"72f2d4fc76d64f4b81e9c713e872df9d9dba3f65cf1ed5673b0b3da9ca484779da091e11c0627c0c1f46f211bb02ca838a44644594073abaa7001523" \
|
7
|
+
"94d9d27b8c996294be08843066e2a20724f9f565d18235af6d6a70cae0021118a36248abe019b261be18b41b55aeb5efa99d82d78c4cdf914447b86b" \
|
8
|
+
"dfa8c2e71869dd11ba0f07ef3a706e4210906ab7185331c8170e2e4221b1c5a8a7344dbb314d3431b16ec0097d6735cbf1aca08230b03df55f65ce90" \
|
9
|
+
"8f641c010d3d049c9bc1c54f7b5aca0715abba7dad2b44270e79b5f3a867da03e45ddf3c4193e513ed6c0d2006452b8a149cf6dc4f506312ad5bb664" \
|
10
|
+
"c51ed3339225a574b769ca32a4b927b00d3f6e8b672ebbf8255c08b9077ffa8cede10000000000000000000000000000000000000000000000000000" \
|
11
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
12
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
13
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
14
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
15
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
16
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
17
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
18
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
19
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
20
|
+
"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" \
|
21
|
+
"00000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
22
|
+
].pack("H*")
|
23
|
+
|
24
|
+
# ngtcp2
|
25
|
+
curl_example_2 = [
|
26
|
+
"cf0000000114ffd7b7163e917c1564d3d466140e55786193ad2614bb91e0a62b38bc5636a092b3bedddd6c2fb3a634008000047c0f37f579dfb9d2f7" \
|
27
|
+
"72adec386b2fa74b17f869dfe69c7fdd43f573c1a7da1a1f8933b74fd4bee9857a9c7ae5c6bf901dee6e89cd36534605e415f7362af8cdeb8dce366b" \
|
28
|
+
"686a12c5926b67cd4eb51a971901acd26065adc289fc880ef571a6be517106b4289bc0e88c55ee359bde58d03b3d51c3f1bbdff3533992b392213266" \
|
29
|
+
"0693b769f62cbeef2e04894fa6b7a07754bad53e59c41e997193fa59a31d3d28bb2fe7f92fde06c95e95bd987b9f2931ce3d099e3a6d81063f27f0a9" \
|
30
|
+
"39f968dec45630822d16a4d21d2b64826f58accec67895dca92e288ab0cf8e499042bb8610d27ef93f06df0baa756c1f6f4f2d547f0225469c5dec8d" \
|
31
|
+
"f30f2e5d446a2fa46282493c736cb64b7e7915ee9b8da09c2923438af552a7486d97ec86ccabace868165e0a7537599e3bfacd3014e1c440713e3e45" \
|
32
|
+
"1fcd4a69aa5bd4e4bdaacc36c677467557a858563f6d1fcfdc2e90e393fb408a08fccb1dc5ab3d34380b8c19a866b7a2e02b6d4a790b8c1affc60441" \
|
33
|
+
"8f2f9910955a9a7bd9863fae2b88ca154205902c46d02c0cd77b94b60246a4c30b2981776c8d81f02a2ef99650e15c23393dfe2e9a331a82b9dadd0c" \
|
34
|
+
"a0ccde5eb1076679d341073d58fbb3ff0a92d7dc22175a8b005e412b3c46d0b9d46aa61ab9aa1adfcf91435d549986ef3c93c84ab24c5712de9bf459" \
|
35
|
+
"c5932506be63f66f8329c465d047aeea3d6f955222cad794b2260aa7bab3e58898261c5e4e120341cc06a770b6b5080160f9106d597637f3c70e1c2f" \
|
36
|
+
"bd11061ed5e222fa0fb2c1168b3e4c73379b1ffc483565faa70af74ee43f7021270d8e0539588bc81676a38020da2928f51eacad7fb7e701d5f733d9" \
|
37
|
+
"7b4d9268110ba3e32d3080a560346487f99c4250ef8632e8078170913ffa3074f3e2f68d807a465fda271580a76ba99b67580e2549c0e895fea9329d" \
|
38
|
+
"df6dbb8f2c23032f84ad84dd51f812c2666b7d073ee73abb642005419325d9cd22b5c18fac3ac5bc0031c6aad50006ac9ee9dddf8fcdf8f58c261a5b" \
|
39
|
+
"86432f3994e8455dbc7b2d8885db3fec3f1a8e1f880a17531896d3d8f7014c4dc13ae757836ecbd2e69c628bf2d539d7aae293e127cd565426e65a07" \
|
40
|
+
"5cd4f5ff42a7d6df2c1743fb68922e09103424d0752d92cce6a3301a899050874a4b1255bf1b24f66e047826cbc92c5532ae6c38fbede4d356d98cf0" \
|
41
|
+
"b61acd508c8e5b9d64b83b84870b62065e9101831bbdbd61bac77edd2971b371a8c39fd79c00c2c3fd9e8f2c0480815d115d34e57a27bf963bd55748" \
|
42
|
+
"491c86f4918b4b84c4ca497a7da6e04a8aa3610c4b02c9336c80a87e6198800bacead5308b2b3f1d371faefb54dc13386539d27ae1acd01c60427d9b" \
|
43
|
+
"3db437b91b8e89c804c28739f9f2f16a63ef2538673ff78ab13cbcb4fba64c45b8dfc54901b3bf09b0d0931b800c07782a044f27a370f11183d7c7ec" \
|
44
|
+
"429406bfa84d67dfbd1622331be2e8ee1787a1ac27b994812df08fd59e2ba6e70773978d6d4373d92e740d921338120772f26d720957d7797bbdd0c3" \
|
45
|
+
"e8b35bf634f2c7609d0fb369524e82105d13d3dd293c62272a81a1fafd91e06a9e89e13346fc0c0e550212fb38e773791e5ebf5cb02cbe757542c489"
|
46
|
+
].pack("H*")
|
47
|
+
|
48
|
+
# msh3
|
49
|
+
curl_example_3 = [
|
50
|
+
"c90000000108653439dec7a03435000044b253edca3c42d9eb3096dc53f2199151ee39c14794fff635d4b38a88f8625434729196b2b0d7a07dc49a7e" \
|
51
|
+
"540c36d4915ca58752432c7c0ffc5cc6933ea6f21665d00d1975b43447173595a6b6e6ea33ea485146a996366c47a5db51ea8a2543123134560bc701" \
|
52
|
+
"866c501390acb349cbcd358632a2f0c22eb403458a1388f351d94994afbf66d3826a1abe0563aba293db1c1ef1c998a37f61bd76bc07fb7c84806efa" \
|
53
|
+
"fc83c5b83390cc97504524afb71d7a1f5001e2b28c9d8c1d56db8b7465ddb008956f62729586cd67133d01739f80abbceb0ddefde61a4721388cab0f" \
|
54
|
+
"e52e6a494f2c970d84ca4dfea36b2bbfa380d2f2c55fb4eacbfd5d9627a4b2a13bc6088f6185eca600df452648343d6491b8466026bb9230096198ae" \
|
55
|
+
"a12785a122bf7c0e2d975f6a023571bc2bddb30f170cf50bb8619ecd7fa5430f632d392d345e44165c9c193d7aaa1f5d21772d31f5fe2f799ec2b74b" \
|
56
|
+
"2b5cf01d0d436d16bb103e61ae3cf314c23e902fdb41d9bcc0f505b097dff110d6533c7818b4cdc06b9f7f0667e2fe46a9ee903a98ba3e4ef130e510" \
|
57
|
+
"c356fe4090ed4fa86e071875dcd92f4e4eeb059826f19eb7342b498b0eb2485b885312e38392cbb3168ea56668101c804c4ed7146cb4a951677abfb9" \
|
58
|
+
"26f9260eec03bcbab39867ab95149147b13f5ad7d0583045e09362f3e4f34711529cf5d691675ee646bd4b67c86f38b0972e593392fdea34b081956a" \
|
59
|
+
"f78e3af3abf03006c585e1a9753cc2022a7c7af89ebb2af272e428c02256d39999ddc1c68c64ac0460a45905d23b9d2ed1f6223631e86c022ad0d161" \
|
60
|
+
"071f876054dc965b8876ae1bb6cbb4526c85edf81f44cbb9151853e313365509a8b874086779e1238f06e528118668883a81b8b948c8f9ce4e867672" \
|
61
|
+
"888e79871b1fb204b821fd9b9a20f7f1ef471cf42b5afa7015edd94bdf0ed4a34326a52bc1b0afaa7f06a10a0cc81bc425abd57cdb2fdf9dce23c29a" \
|
62
|
+
"b9a899c66a99f218c9979de34b72b1c6f6d0cd1d3bd4327f23f49f4ece3e8c0eeeeb6792565a60f2ac458f84cdbffd9a68192709b03b9172f0701000" \
|
63
|
+
"13364ee8af8afebaf95b2d42140c482a7167e2b0b81c83db0d78c50952f02dc211c274946d7279a130cbf090690fa74b130d0985a3b0e420aea25256" \
|
64
|
+
"d20135793fedcc3d1b2e983751115d1f63867f60032b9c4d998d3826ec7168e234990f57e905a36c15e6161360f825d4448a6a7c7579a685a70f4070" \
|
65
|
+
"d23f1ccb8fa27c7c20f7f02fa04888d30f9cb1fdcaf2b8004ecd38e36b5eb780d96991602c2e31c038d716afb178cf70d856f092c11c7b486219c7e9" \
|
66
|
+
"89519badcc25051ff271d53735a6b39676669859255174ae2ba14417cda1a7657004f0225eb042e716c467ed5c9d6756d376aae59cc8c8eb109ce337" \
|
67
|
+
"61f23b608066087021360ef3f1793e7d1566482582672ddd8b2566b739e3f357dea4618853a60d6ccbc91ff4390caef222577aab2034e9eefa931ead" \
|
68
|
+
"1e53c9f147374c7fb5a80583c2dafc4a0f91ed9dfa331fade079d06ef06410e6e18a1b1d9f0dc29b1a13ed0c6fce86e9cba88d2b566b0f13bce9757e" \
|
69
|
+
"d9c35d5bb1c5e9984202d1be90ef3538df613cbd4dd7d0a4f608d6f3de2a4a68ad10a77ff08ec0ab7f5c3efc3f3cb5f24d63353059b9d69e0a47dc74" \
|
70
|
+
"d81edd95ef717fcc28f3c6e2f8ce321bf0aba3fa"
|
71
|
+
].pack("H*")
|
72
|
+
|
73
|
+
[curl_example_1, curl_example_2, curl_example_3].each do |curl_example|
|
74
|
+
buf = Raioquic::Buffer.new(data: curl_example)
|
75
|
+
pp Raioquic::Quic::Packet.pull_quic_header(buf: buf, host_cid_length: 8)
|
76
|
+
puts "\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
# $ bundle exec ruby example/parse_curl_example.rb
|
80
|
+
# #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f836280
|
81
|
+
# @destination_cid="\ec\x1Cc\x91\xA9\x13\t\xC2\x93\x95\xAA\xA5\xC1\x80\xBC",
|
82
|
+
# @integrity_tag="",
|
83
|
+
# @is_long_header=true,
|
84
|
+
# @packet_type=192,
|
85
|
+
# @rest_length=288,
|
86
|
+
# @source_cid="\xDB}\xAF\xC0\xE1\xC1\x04\a\xFFJ\x95'\xA7\x11\x03\xBB~\xB5\x9A\x91",
|
87
|
+
# @token="",
|
88
|
+
# @version=1>
|
89
|
+
#
|
90
|
+
# #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f82cc80
|
91
|
+
# @destination_cid="\xFF\xD7\xB7\x16>\x91|\x15d\xD3\xD4f\x14\x0EUxa\x93\xAD&",
|
92
|
+
# @integrity_tag="",
|
93
|
+
# @is_long_header=true,
|
94
|
+
# @packet_type=192,
|
95
|
+
# @rest_length=1148,
|
96
|
+
# @source_cid="\xBB\x91\xE0\xA6+8\xBCV6\xA0\x92\xB3\xBE\xDD\xDDl/\xB3\xA64",
|
97
|
+
# @token="",
|
98
|
+
# @version=1>
|
99
|
+
#
|
100
|
+
# #<Raioquic::Quic::Packet::QuicHeader:0x00007f844f82c0a0
|
101
|
+
# @destination_cid="e49\xDE\xC7\xA045",
|
102
|
+
# @integrity_tag="",
|
103
|
+
# @is_long_header=true,
|
104
|
+
# @packet_type=192,
|
105
|
+
# @rest_length=1202,
|
106
|
+
# @source_cid="",
|
107
|
+
# @token="",
|
108
|
+
# @version=1>
|
@@ -0,0 +1,202 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
require "forwardable"
|
5
|
+
|
6
|
+
module Raioquic
|
7
|
+
# Raioquic::Buffer
|
8
|
+
# Migrated from auiquic/src/aioquic/buffer.py
|
9
|
+
class Buffer
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
BufferReadError = Class.new(StandardError)
|
13
|
+
BufferWriteError = Class.new(StandardError)
|
14
|
+
|
15
|
+
UINT_VAR_MAX = 0x3fffffffffffffff
|
16
|
+
UINT_VAR_MAX_SIZE = 8
|
17
|
+
|
18
|
+
def_delegator :@buffer, :eof, :eof
|
19
|
+
def_delegator :@buffer, :tell, :tell
|
20
|
+
|
21
|
+
attr_reader :capacity
|
22
|
+
|
23
|
+
# Encode a variable-length unsigned integer.
|
24
|
+
def self.encode_uint_var(value)
|
25
|
+
buf = new(capacity: UINT_VAR_MAX_SIZE)
|
26
|
+
buf.push_uint_var(value)
|
27
|
+
buf.data
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the number of bytes required to encode the given value
|
31
|
+
# as a QUIC variable-length unsigned integer.
|
32
|
+
def self.size_uint_var(value)
|
33
|
+
if value <= 0x3f # rubocop:disable Style/GuardClause
|
34
|
+
return 1
|
35
|
+
elsif value <= 0x3fff
|
36
|
+
return 2
|
37
|
+
elsif value <= 0x3fffffff
|
38
|
+
return 4
|
39
|
+
elsif value <= 0x3fffffffffffffff
|
40
|
+
return 8
|
41
|
+
else
|
42
|
+
raise ValueError, "Integer is too big for a variable-length integer"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(capacity: nil, data: "")
|
47
|
+
@position = 0 # bytes count
|
48
|
+
@buffer = StringIO.open((+data).force_encoding(Encoding::ASCII_8BIT), "rb+:ASCII-8bit:ASCII-8BIT")
|
49
|
+
@capacity = capacity || @buffer.size
|
50
|
+
end
|
51
|
+
|
52
|
+
def dealloc
|
53
|
+
# empty
|
54
|
+
end
|
55
|
+
|
56
|
+
def data
|
57
|
+
data_slice(start: 0, ends: tell)
|
58
|
+
end
|
59
|
+
|
60
|
+
def seek(offset)
|
61
|
+
check_read_bounds(offset)
|
62
|
+
@buffer.seek(offset)
|
63
|
+
rescue Errno::EINVAL
|
64
|
+
raise BufferReadError
|
65
|
+
end
|
66
|
+
|
67
|
+
# def capacity
|
68
|
+
# @capacity
|
69
|
+
# end
|
70
|
+
|
71
|
+
# NOTE: "end" is reserved keyword in Ruby
|
72
|
+
def data_slice(start:, ends:)
|
73
|
+
orig_str = @buffer.string
|
74
|
+
|
75
|
+
raise BufferReadError, "Read out of bounds" if start < 0 || @buffer.size < start || ends < 0 || @buffer.size < ends || ends < start
|
76
|
+
|
77
|
+
orig_str.byteslice(start, ends - start)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Pull bytes.
|
81
|
+
def pull_bytes(length)
|
82
|
+
raise BufferReadError if @buffer.size < 1
|
83
|
+
|
84
|
+
@buffer.read(length)
|
85
|
+
rescue EOFError, ArgumentError
|
86
|
+
raise BufferReadError
|
87
|
+
end
|
88
|
+
|
89
|
+
# Pull an 8-bit unsigned integer.
|
90
|
+
def pull_uint8
|
91
|
+
@buffer.readpartial(1).unpack1("C")
|
92
|
+
rescue EOFError
|
93
|
+
raise BufferReadError
|
94
|
+
end
|
95
|
+
|
96
|
+
# Pull a 16-bit unsigned integer.
|
97
|
+
def pull_uint16
|
98
|
+
@buffer.readpartial(2).unpack1("n")
|
99
|
+
rescue EOFError
|
100
|
+
raise BufferReadError
|
101
|
+
end
|
102
|
+
|
103
|
+
# Pull a 32-bit unsigned integer.
|
104
|
+
def pull_uint32
|
105
|
+
@buffer.readpartial(4).unpack1("N")
|
106
|
+
rescue EOFError
|
107
|
+
raise BufferReadError
|
108
|
+
end
|
109
|
+
|
110
|
+
# Pull a 64-bit unsigned integer.
|
111
|
+
def pull_uint64
|
112
|
+
@buffer.readpartial(8).unpack1("Q>")
|
113
|
+
rescue EOFError
|
114
|
+
raise BufferReadError
|
115
|
+
end
|
116
|
+
|
117
|
+
# Pull a QUIC variable-length unsigned integer.
|
118
|
+
def pull_uint_var
|
119
|
+
check_read_bounds(1)
|
120
|
+
first = pull_uint8
|
121
|
+
case first >> 6
|
122
|
+
when 0
|
123
|
+
first & 0x3f
|
124
|
+
when 1
|
125
|
+
check_read_bounds(1)
|
126
|
+
second = pull_uint8
|
127
|
+
((first & 0x3f) << 8) | (second)
|
128
|
+
when 2
|
129
|
+
check_read_bounds(3)
|
130
|
+
second = pull_uint8
|
131
|
+
third = pull_uint8
|
132
|
+
forth = pull_uint8
|
133
|
+
((first & 0x3f) << 24) | (second << 16) | (third << 8) | forth
|
134
|
+
else
|
135
|
+
check_read_bounds(7)
|
136
|
+
b2 = pull_uint8
|
137
|
+
b3 = pull_uint8
|
138
|
+
b4 = pull_uint8
|
139
|
+
b5 = pull_uint8
|
140
|
+
b6 = pull_uint8
|
141
|
+
b7 = pull_uint8
|
142
|
+
b8 = pull_uint8
|
143
|
+
((first & 0x3f) << 56) | (b2 << 48) | (b3 << 40) | (b4 << 32) | (b5 << 24) | (b6 << 16) | (b7 << 8) | b8
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def push_bytes(value)
|
148
|
+
raise BufferWriteError if value.bytesize > [@buffer.size, @capacity].max
|
149
|
+
|
150
|
+
@buffer << value
|
151
|
+
end
|
152
|
+
|
153
|
+
def push_uint8(value)
|
154
|
+
@buffer << [value].pack("C")
|
155
|
+
end
|
156
|
+
|
157
|
+
def push_uint16(value)
|
158
|
+
@buffer << [value].pack("n")
|
159
|
+
end
|
160
|
+
|
161
|
+
def push_uint32(value)
|
162
|
+
@buffer << [value].pack("N")
|
163
|
+
end
|
164
|
+
|
165
|
+
def push_uint64(value)
|
166
|
+
@buffer << [value].pack("Q>")
|
167
|
+
end
|
168
|
+
|
169
|
+
def push_uint_var(value)
|
170
|
+
if value <= 0x3f
|
171
|
+
check_read_bounds(1)
|
172
|
+
push_uint8(value)
|
173
|
+
elsif value <= 0x3fff
|
174
|
+
check_read_bounds(2)
|
175
|
+
push_uint8((value >> 8) | 0x40)
|
176
|
+
push_uint8(value & 0xff)
|
177
|
+
elsif value <= 0x3fffffff
|
178
|
+
check_read_bounds(4)
|
179
|
+
push_uint8((value >> 24) | 0x80)
|
180
|
+
push_uint8((value >> 16) & 0xff)
|
181
|
+
push_uint8((value >> 8) & 0xff)
|
182
|
+
push_uint8(value)
|
183
|
+
elsif value <= 0x3fffffffffffffff
|
184
|
+
check_read_bounds(8)
|
185
|
+
push_uint8((value >> 56) | 0xc0)
|
186
|
+
push_uint8((value >> 48) & 0xff)
|
187
|
+
push_uint8((value >> 40) & 0xff)
|
188
|
+
push_uint8((value >> 32) & 0xff)
|
189
|
+
push_uint8((value >> 24) & 0xff)
|
190
|
+
push_uint8((value >> 16) & 0xff)
|
191
|
+
push_uint8((value >> 8) & 0xff)
|
192
|
+
push_uint8(value & 0xff)
|
193
|
+
else
|
194
|
+
raise ValueError, "Integer is too big for a variable-length integer"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
private def check_read_bounds(length)
|
199
|
+
raise BufferReadError, "Read out of bounds" if [@buffer.size, @capacity].max < length
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# CoreExt (like active support)
|
4
|
+
|
5
|
+
# CoreExt for String class
|
6
|
+
class String
|
7
|
+
# Convert big endian bytes to integer (unsigned, up to 4 byte)
|
8
|
+
def bytes_to_int
|
9
|
+
case bytesize
|
10
|
+
when 0
|
11
|
+
0
|
12
|
+
when 1
|
13
|
+
("\x00" + self).unpack1("n*").to_i # rubocop:disable Style/StringConcatenation
|
14
|
+
when 2
|
15
|
+
unpack1("n*").to_i
|
16
|
+
when 3
|
17
|
+
("\x00" + self).unpack1("N*").to_i # rubocop:disable Style/StringConcatenation
|
18
|
+
when 4
|
19
|
+
unpack1("N*").to_i
|
20
|
+
else
|
21
|
+
raise ArgumentError, "#{bytesize} bytes is not supported"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# CoreExt for Integer class
|
27
|
+
class Integer
|
28
|
+
# Convert integer to specific byte size string (unsigned, big endian)
|
29
|
+
def to_bytes(bytesize) # rubocop:disable Metrics/CyclomaticComplexity
|
30
|
+
raise ArgumentError if self > 4294967295 # 5 bytes
|
31
|
+
|
32
|
+
template = case bytesize
|
33
|
+
when 0
|
34
|
+
nil
|
35
|
+
when 1
|
36
|
+
"C*"
|
37
|
+
when 2
|
38
|
+
"n*"
|
39
|
+
when 3..4
|
40
|
+
"N*"
|
41
|
+
else
|
42
|
+
raise ArgumentError, "#{bytesize} bytes is too big"
|
43
|
+
end
|
44
|
+
return "" if template.nil?
|
45
|
+
|
46
|
+
[self].pack(template).then do |str|
|
47
|
+
if bytesize == 3
|
48
|
+
str.byteslice(1, 3).to_s
|
49
|
+
else
|
50
|
+
str
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "backend/aead"
|
4
|
+
|
5
|
+
module Raioquic
|
6
|
+
module Crypto
|
7
|
+
# Raioquic::Crypto::AESGCM
|
8
|
+
# Migrated from pyca/cryptography/src/cryptography/hazmat/primitives/ciphers/aead.py
|
9
|
+
class AESGCM
|
10
|
+
attr_reader :key
|
11
|
+
|
12
|
+
MAX_SIZE = (2**31) - 1
|
13
|
+
OverflowError = Class.new(StandardError)
|
14
|
+
|
15
|
+
def initialize(key)
|
16
|
+
raise TypeError unless key.is_a? String
|
17
|
+
raise ValueError, "AESGCM key must be 128, 192, or 256 bits." unless [16, 24, 32].include?(key.length)
|
18
|
+
|
19
|
+
@key = key
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.generate_key(bit_length)
|
23
|
+
raise TypeError, "bit_length must be an integer" unless bit_length.is_a? Integer
|
24
|
+
raise ValueError, "bit_length must be 128, 192, or 256" unless [128, 192, 256].include?(bit_length)
|
25
|
+
|
26
|
+
Random.urandom(bit_length / 8)
|
27
|
+
end
|
28
|
+
|
29
|
+
def encrypt(nonce:, data:, associated_data: "")
|
30
|
+
raise OverflowError, "Data or associated data too long. Max 2**31 - 1 bytes" if data.length > MAX_SIZE || associated_data.length > MAX_SIZE
|
31
|
+
|
32
|
+
check_nonce(nonce)
|
33
|
+
validate_length(nonce: nonce, data: data)
|
34
|
+
|
35
|
+
Backend::Aead.encrypt(cipher: self, key: @key, nonce: nonce, data: data, associated_data: [associated_data], tag_length: 16)
|
36
|
+
end
|
37
|
+
|
38
|
+
def decrypt(nonce:, data:, associated_data: "")
|
39
|
+
Backend::Aead.decrypt(cipher: self, key: @key, nonce: nonce, data: data, associated_data: [associated_data], tag_length: 16)
|
40
|
+
end
|
41
|
+
|
42
|
+
private def validate_length(nonce:, data:)
|
43
|
+
l_val = 15 - nonce.length
|
44
|
+
raise ValueError, "Data too long for nonce" if 2 * (8 * l_val) < data.length
|
45
|
+
end
|
46
|
+
|
47
|
+
private def check_nonce(nonce)
|
48
|
+
raise ValueError, "Nonce must be 12 bytes" if nonce.length != 12
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module Raioquic
|
6
|
+
module Crypto
|
7
|
+
module Backend
|
8
|
+
# Raioquic's Crypto::Backend is only OpenSSL
|
9
|
+
# Migrated from pyca/cryptography/src/cryptography/hazmat/backends/openssl/aead.py
|
10
|
+
class Aead
|
11
|
+
def self.aead_cipher_name(cipher)
|
12
|
+
case cipher
|
13
|
+
# when ChaCha20Poly1305
|
14
|
+
# when AESCCM
|
15
|
+
# "aes-#{cipher.key.length}-ccm"
|
16
|
+
when ::Raioquic::Crypto::AESGCM
|
17
|
+
"aes-#{cipher.key.length * 8}-gcm"
|
18
|
+
else
|
19
|
+
raise RuntimeError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
private_class_method :aead_cipher_name
|
23
|
+
|
24
|
+
def self.encrypt(cipher:, key:, nonce:, data:, associated_data: [], tag_length:)
|
25
|
+
cipher_name = aead_cipher_name(cipher)
|
26
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
27
|
+
cipher.encrypt
|
28
|
+
cipher.key = key
|
29
|
+
cipher.iv = nonce
|
30
|
+
cipher.auth_data = associated_data.join
|
31
|
+
encrypted = cipher.update(data) + cipher.final
|
32
|
+
tag = cipher.auth_tag(tag_length)
|
33
|
+
return encrypted + tag
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.decrypt(cipher:, key:, nonce:, data:, associated_data: [], tag_length:)
|
37
|
+
cipher_name = aead_cipher_name(cipher)
|
38
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
39
|
+
tag = data.slice(-tag_length, tag_length)
|
40
|
+
encryped = data.slice(0, data.length - tag_length)
|
41
|
+
cipher.decrypt
|
42
|
+
cipher.key = key
|
43
|
+
cipher.iv = nonce
|
44
|
+
cipher.auth_tag = tag
|
45
|
+
cipher.auth_data = associated_data.join
|
46
|
+
decrypted = cipher.update(encryped) + cipher.final
|
47
|
+
return decrypted
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
|
5
|
+
module Raioquic
|
6
|
+
module Quic
|
7
|
+
# Raioquic::Quic::Configuration
|
8
|
+
# Migrated from aioquic/src/aioquic/quic/configuration.py
|
9
|
+
# A QUIC configration
|
10
|
+
class QuicConfiguration
|
11
|
+
attr_accessor :alpn_protocols
|
12
|
+
attr_accessor :connection_id_length
|
13
|
+
attr_accessor :idle_timeout
|
14
|
+
attr_accessor :is_client
|
15
|
+
attr_accessor :max_data
|
16
|
+
attr_accessor :max_stream_data
|
17
|
+
attr_accessor :quic_logger
|
18
|
+
attr_accessor :secrets_log_file
|
19
|
+
attr_accessor :server_name
|
20
|
+
attr_accessor :session_ticket
|
21
|
+
attr_accessor :cadata
|
22
|
+
attr_accessor :cafile
|
23
|
+
attr_accessor :capath
|
24
|
+
attr_accessor :certificate
|
25
|
+
attr_accessor :certificate_chain
|
26
|
+
attr_accessor :cipher_suites
|
27
|
+
attr_accessor :initial_rtt
|
28
|
+
attr_accessor :max_datagram_frame_size
|
29
|
+
attr_accessor :private_key
|
30
|
+
attr_accessor :quantam_readiness_test
|
31
|
+
attr_accessor :supported_versions
|
32
|
+
attr_accessor :verify_mode
|
33
|
+
|
34
|
+
def initialize(**kwargs) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
35
|
+
@alpn_protocols = kwargs[:alpn_protocols]
|
36
|
+
@connection_id_length = kwargs[:connection_id_length] || 8
|
37
|
+
@idle_timeout = kwargs[:idle_timeout] || 60.0
|
38
|
+
@is_client = kwargs[:is_client]
|
39
|
+
@max_data = kwargs[:max_data] || 1048576
|
40
|
+
@max_stream_data = kwargs[:max_stream_data] || 1048576
|
41
|
+
@quic_logger = kwargs[:quic_logger]
|
42
|
+
@secrets_log_file = kwargs[:secrets_log_file]
|
43
|
+
@server_name = kwargs[:server_name]
|
44
|
+
@session_ticket = kwargs[:session_ticket]
|
45
|
+
@cadata = kwargs[:cadata]
|
46
|
+
@cafile = kwargs[:cafile]
|
47
|
+
@capath = kwargs[:capath]
|
48
|
+
@certificate = kwargs[:certificate]
|
49
|
+
@certificate_chain = kwargs[:certificate_chain]
|
50
|
+
@cipher_suites = kwargs[:cipher_suites]
|
51
|
+
@initial_rtt = kwargs[:initial_rtt] || 0.1
|
52
|
+
@max_datagram_frame_size = kwargs[:max_datagram_frame_size]
|
53
|
+
@private_key = kwargs[:private_key]
|
54
|
+
@quantam_readiness_test = kwargs[:quantam_readiness_test] || false
|
55
|
+
@supported_versions = kwargs[:supported_versions] || [Packet::QuicProtocolVersion::VERSION_1]
|
56
|
+
@verify_mode = kwargs[:verify_mode]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Load a private key and the corresponding certificate.
|
60
|
+
def load_cert_chain(certfile, keyfile = nil, password = nil)
|
61
|
+
boundary = "-----BEGIN PRIVATE KEY-----\n"
|
62
|
+
cert_body = File.read(certfile)
|
63
|
+
certs = OpenSSL::X509::Certificate.load(cert_body)
|
64
|
+
|
65
|
+
@certificate = certs[0]
|
66
|
+
@certificate_chain = certs[1..] if certs.length > 1
|
67
|
+
|
68
|
+
@private_key = OpenSSL::PKey.read(cert_body) if cert_body.include?(boundary)
|
69
|
+
|
70
|
+
@private_key = OpenSSL::PKey.read(File.read(keyfile), password) if keyfile
|
71
|
+
end
|
72
|
+
|
73
|
+
# Load a set of "certification authority" (CA) certificates used to validate other peer's certificates.
|
74
|
+
def load_verify_locations(cafile: nil, capath: nil, cadata: nil)
|
75
|
+
@cafile = cafile
|
76
|
+
@capath = capath
|
77
|
+
@cadata = cadata
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|