raioquic 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|