quic 0.0.1 → 0.0.2

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.
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Server certificate verification demo: handshake with a public QUIC server
4
+ # under a few QUIC::Settings verification configurations and report how each
5
+ # one ends.
6
+ #
7
+ # 1. The defaults: verify_mode :peer against the system CA store.
8
+ # 2. ca_file holding only a throwaway CA generated on the spot. The server's
9
+ # chain does not lead to it, so the handshake fails with
10
+ # QUIC::Error::CertificateVerifyFailed.
11
+ # 3. verify_mode :none, which skips verification entirely.
12
+ # 4. ca_file from VERIFY_CA_FILE, when that variable is set.
13
+ #
14
+ # Override the target via env vars:
15
+ # VERIFY_HOST (default cloudflare-quic.com)
16
+ # VERIFY_PORT (default 443)
17
+ # VERIFY_ALPN (default h3)
18
+ # VERIFY_CA_FILE (default unset: step 4 is skipped)
19
+ #
20
+ # Each attempt connects its socket to the address its client resolved, so
21
+ # both talk to the same peer (see examples/handshake_demo.rb).
22
+ #
23
+ # Run with: bundle exec ruby examples/verify_demo.rb
24
+
25
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
26
+ require "quic"
27
+ require "socket"
28
+ require "openssl"
29
+ require "tmpdir"
30
+
31
+ TARGET_HOST = ENV.fetch("VERIFY_HOST", "cloudflare-quic.com")
32
+ TARGET_PORT = Integer(ENV.fetch("VERIFY_PORT", "443"))
33
+ TARGET_ALPN = ENV.fetch("VERIFY_ALPN", "h3")
34
+ TARGET_CA_FILE = ENV["VERIFY_CA_FILE"]
35
+
36
+ # A self-signed CA that no real server chains to, written as PEM into dir.
37
+ def write_untrusted_ca(dir)
38
+ key = OpenSSL::PKey::EC.generate("prime256v1")
39
+ name = OpenSSL::X509::Name.parse("/CN=quic-ruby verify_demo CA")
40
+ cert = OpenSSL::X509::Certificate.new
41
+ cert.version = 2
42
+ cert.serial = 1
43
+ cert.subject = name
44
+ cert.issuer = name
45
+ cert.public_key = key
46
+ cert.not_before = Time.now - 60
47
+ cert.not_after = Time.now + 3600
48
+ factory = OpenSSL::X509::ExtensionFactory.new(cert, cert)
49
+ cert.add_extension(factory.create_extension("basicConstraints", "CA:TRUE", true))
50
+ cert.add_extension(factory.create_extension("keyUsage", "keyCertSign,cRLSign", true))
51
+ cert.sign(key, OpenSSL::Digest.new("SHA256"))
52
+ File.join(dir, "untrusted-ca.pem").tap { |path| File.write(path, cert.to_pem) }
53
+ end
54
+
55
+ # verify_result is an X509_V_ERR_* value; show the matching Ruby constant.
56
+ def verify_result_name(value)
57
+ name = OpenSSL::X509.constants.grep(/\AV_ERR_/).find { |c| OpenSSL::X509.const_get(c) == value }
58
+ name ? "OpenSSL::X509::#{name}" : "unknown"
59
+ end
60
+
61
+ def handshake(label, settings)
62
+ client = QUIC::Connection::Client.new(
63
+ host: TARGET_HOST, port: TARGET_PORT, address_family: :inet, settings: settings
64
+ )
65
+ addr = client.remote_address
66
+ sock = UDPSocket.new(Socket::AF_INET)
67
+ sock.connect(addr.ip_address, addr.ip_port)
68
+
69
+ puts "== #{label} (#{addr.ip_address}:#{addr.ip_port})"
70
+ puts " verify_mode=#{settings.verify_mode.inspect} ca_file=#{settings.ca_file.inspect} ca_path=#{settings.ca_path.inspect}"
71
+ t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ client.bind(sock).run
73
+ elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round(1)
74
+ puts " handshake completed (#{elapsed_ms}ms)"
75
+ client.close
76
+ rescue QUIC::Error::CertificateVerifyFailed => e
77
+ puts " #{e.class}: #{e.message}"
78
+ puts " verify_result=#{e.verify_result.inspect} (#{verify_result_name(e.verify_result)})"
79
+ puts " tls_alert=#{e.tls_alert.inspect}"
80
+ rescue QUIC::Error::CryptoError => e
81
+ puts " #{e.class}: #{e.message} (tls_alert=#{e.tls_alert.inspect})"
82
+ rescue QUIC::Error => e
83
+ puts " #{e.class}: #{e.message}"
84
+ ensure
85
+ sock&.close
86
+ puts
87
+ end
88
+
89
+ puts "libcrypto: #{QUIC.library_versions[:openssl]}"
90
+ puts "Target: #{TARGET_HOST}:#{TARGET_PORT}, alpn #{TARGET_ALPN}"
91
+ puts
92
+
93
+ base = QUIC::Settings.default.with(alpn: [TARGET_ALPN])
94
+
95
+ handshake("system CA store (default)", base)
96
+
97
+ Dir.mktmpdir do |dir|
98
+ handshake("ca_file with an unrelated CA", base.with(ca_file: write_untrusted_ca(dir)))
99
+ end
100
+
101
+ handshake("verify_mode :none (not verified)", base.with(verify_mode: :none))
102
+
103
+ if TARGET_CA_FILE && !TARGET_CA_FILE.empty?
104
+ handshake("ca_file from VERIFY_CA_FILE", base.with(ca_file: TARGET_CA_FILE))
105
+ end