io-endpoint 0.17.2 → 0.18.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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/context/getting-started.md +34 -0
- data/context/index.yaml +2 -0
- data/lib/io/endpoint/ssl_endpoint.rb +15 -73
- data/lib/io/endpoint/tls/certificates.rb +35 -0
- data/lib/io/endpoint/tls/configuration.rb +82 -0
- data/lib/io/endpoint/tls/openssl.rb +66 -0
- data/lib/io/endpoint/tls/trust_store.rb +70 -0
- data/lib/io/endpoint/version.rb +1 -1
- data/lib/io/endpoint.rb +3 -0
- data/readme.md +25 -7
- data/releases.md +6 -0
- data.tar.gz.sig +0 -0
- metadata +24 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 01c66c778ce2edc823029e630d48e6240d7540fc277604e47341fba40a2a0377
|
|
4
|
+
data.tar.gz: 4da0e58ab7270e0edde4442049dc6e6fc6a5b818798da77383c2e99ef6fa2cfe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2b1cf8108ad055f3e40ec41e4ee82d510c72d2ae17bd49bc895c4944ada35a9d1c45e97ea44aab6a7bc75d8bdc0f5c808009fe5012d843e6b591af1fdbef62e9
|
|
7
|
+
data.tar.gz: 0e0eee1759843f2c045be325730706ee85c730603172b76c252955bb2121f3c962bb4a60fbf6617e67d7ebc50ba83b0b3e304ba7d9d2c468c0fa77dd35b0bc46
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/context/getting-started.md
CHANGED
|
@@ -111,3 +111,37 @@ endpoint.connect do |socket|
|
|
|
111
111
|
puts response
|
|
112
112
|
end
|
|
113
113
|
```
|
|
114
|
+
|
|
115
|
+
### Configuring TLS Certificate Material
|
|
116
|
+
|
|
117
|
+
Use {ruby IO::Endpoint::TLS::Configuration} to provide certificate and private key material without coupling application configuration to OpenSSL. Certificate material is supplied as PEM content rather than file paths, so it can be loaded from files, environment variables, or secret stores:
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
trust_store = IO::Endpoint::TLS::TrustStore.parse(root_certificates)
|
|
121
|
+
certificate_chain = IO::Endpoint::TLS::Certificates.parse(certificate_chain_bundle)
|
|
122
|
+
|
|
123
|
+
tls_configuration = IO::Endpoint::TLS::Configuration.new(
|
|
124
|
+
trust_store: trust_store,
|
|
125
|
+
certificate_chain: certificate_chain,
|
|
126
|
+
private_key: private_key,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
endpoint = IO::Endpoint.ssl(
|
|
130
|
+
"example.com",
|
|
131
|
+
443,
|
|
132
|
+
hostname: "example.com",
|
|
133
|
+
tls_configuration: tls_configuration,
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Use `TrustStore.new(certificates: certificates)` when certificates are already represented as an array of individual PEM strings. Use `TrustStore.parse(certificate_bundle)` to split a concatenated PEM bundle into that canonical representation, or `TrustStore.load("/path/to/certificates.pem")` to load and parse a bundle from a file. Options such as `system_certificates: true` are forwarded from `load` to `parse`.
|
|
138
|
+
|
|
139
|
+
The local certificate chain is an ordered array of individual PEM strings, with the leaf certificate followed by any intermediate certificates. The root certificate is normally omitted. Use `Certificates.parse(certificate_bundle)` to split a concatenated bundle while preserving that order.
|
|
140
|
+
|
|
141
|
+
Set `system_certificates: true` to include system-provided trusted certificates. System and custom certificates can be combined in the same trust store.
|
|
142
|
+
|
|
143
|
+
The SSL endpoint converts the trust store into an OpenSSL certificate store and the complete configuration into an OpenSSL context. Other endpoint implementations can consume the same trust, identity, and verification configuration using their native TLS implementation.
|
|
144
|
+
|
|
145
|
+
The OpenSSL conversion is also available directly using `IO::Endpoint::TLS::OpenSSL.build_certificate_store(trust_store)`.
|
|
146
|
+
|
|
147
|
+
For a server which requires clients to provide a trusted certificate, set `verification: :required`. Use `:peer` to verify a certificate when the peer provides one, and `:none` to explicitly disable peer verification. When a trust store is supplied and no policy is specified, peer verification is enabled by default. On client connections with a hostname, peer verification also checks that the certificate identifies that hostname.
|
data/context/index.yaml
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
---
|
|
4
4
|
description: Provides a separation of concerns interface for IO endpoints.
|
|
5
5
|
metadata:
|
|
6
|
+
bug_tracker_uri: https://github.com/socketry/io-endpoint/issues
|
|
7
|
+
changelog_uri: https://github.com/socketry/io-endpoint/blob/main/releases.md
|
|
6
8
|
documentation_uri: https://socketry.github.io/io-endpoint
|
|
7
9
|
source_code_uri: https://github.com/socketry/io-endpoint.git
|
|
8
10
|
files:
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
require_relative "host_endpoint"
|
|
7
7
|
require_relative "generic"
|
|
8
|
+
require_relative "tls/openssl"
|
|
8
9
|
|
|
9
10
|
require "openssl"
|
|
10
11
|
|
|
@@ -22,77 +23,6 @@ module OpenSSL
|
|
|
22
23
|
end
|
|
23
24
|
end
|
|
24
25
|
|
|
25
|
-
# Represents a module that forwards socket methods to the underlying IO object.
|
|
26
|
-
module SocketForwarder
|
|
27
|
-
unless method_defined?(:close_on_exec=)
|
|
28
|
-
# Set whether the socket should be closed on exec.
|
|
29
|
-
# @parameter value [Boolean] Whether to close on exec.
|
|
30
|
-
def close_on_exec=(value)
|
|
31
|
-
to_io.close_on_exec = value
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
unless method_defined?(:local_address)
|
|
36
|
-
# Get the local address of the socket.
|
|
37
|
-
# @returns [Addrinfo] The local address.
|
|
38
|
-
def local_address
|
|
39
|
-
to_io.local_address
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
unless method_defined?(:remote_address)
|
|
44
|
-
# Get the remote address of the socket.
|
|
45
|
-
# @returns [Addrinfo] The remote address.
|
|
46
|
-
def remote_address
|
|
47
|
-
to_io.remote_address
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
unless method_defined?(:wait)
|
|
52
|
-
# Wait for the socket to become ready.
|
|
53
|
-
# @parameter arguments [Array] Arguments to pass to the underlying IO wait method.
|
|
54
|
-
# @returns [IO, nil] The socket if ready, nil otherwise.
|
|
55
|
-
def wait(*arguments)
|
|
56
|
-
to_io.wait(*arguments)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
unless method_defined?(:wait_readable)
|
|
61
|
-
# Wait for the socket to become readable.
|
|
62
|
-
# @parameter arguments [Array] Arguments to pass to the underlying IO wait_readable method.
|
|
63
|
-
# @returns [IO, nil] The socket if readable, nil otherwise.
|
|
64
|
-
def wait_readable(*arguments)
|
|
65
|
-
to_io.wait_readable(*arguments)
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
unless method_defined?(:wait_writable)
|
|
70
|
-
# Wait for the socket to become writable.
|
|
71
|
-
# @parameter arguments [Array] Arguments to pass to the underlying IO wait_writable method.
|
|
72
|
-
# @returns [IO, nil] The socket if writable, nil otherwise.
|
|
73
|
-
def wait_writable(*arguments)
|
|
74
|
-
to_io.wait_writable(*arguments)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
if IO.method_defined?(:timeout)
|
|
79
|
-
unless method_defined?(:timeout)
|
|
80
|
-
# Get the timeout for socket operations.
|
|
81
|
-
# @returns [Numeric, nil] The timeout value.
|
|
82
|
-
def timeout
|
|
83
|
-
to_io.timeout
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
unless method_defined?(:timeout=)
|
|
88
|
-
# Set the timeout for socket operations.
|
|
89
|
-
# @parameter value [Numeric, nil] The timeout value.
|
|
90
|
-
def timeout=(value)
|
|
91
|
-
to_io.timeout = value
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
26
|
end
|
|
97
27
|
end
|
|
98
28
|
|
|
@@ -102,6 +32,7 @@ module IO::Endpoint
|
|
|
102
32
|
# Initialize a new SSL endpoint.
|
|
103
33
|
# @parameter endpoint [Generic] The underlying endpoint to wrap with SSL.
|
|
104
34
|
# @option ssl_context [OpenSSL::SSL::SSLContext, nil] An optional SSL context to use.
|
|
35
|
+
# @option tls_configuration [TLS::Configuration, nil] Transport-neutral TLS certificate and verification configuration.
|
|
105
36
|
# @parameter options [Hash] Additional options including `:ssl_params` and `:hostname`.
|
|
106
37
|
def initialize(endpoint, **options)
|
|
107
38
|
super(**options)
|
|
@@ -150,6 +81,12 @@ module IO::Endpoint
|
|
|
150
81
|
@options[:ssl_params]
|
|
151
82
|
end
|
|
152
83
|
|
|
84
|
+
# Get the transport-neutral TLS configuration from options.
|
|
85
|
+
# @returns [TLS::Configuration, nil] The TLS configuration if specified.
|
|
86
|
+
def tls_configuration
|
|
87
|
+
@options[:tls_configuration]
|
|
88
|
+
end
|
|
89
|
+
|
|
153
90
|
# Build an SSL context with configured parameters.
|
|
154
91
|
# @parameter context [OpenSSL::SSL::SSLContext] An optional SSL context to configure.
|
|
155
92
|
# @returns [OpenSSL::SSL::SSLContext] The configured SSL context.
|
|
@@ -158,6 +95,10 @@ module IO::Endpoint
|
|
|
158
95
|
context.set_params(params)
|
|
159
96
|
end
|
|
160
97
|
|
|
98
|
+
if tls_configuration = self.tls_configuration
|
|
99
|
+
TLS::OpenSSL.apply(context, tls_configuration, hostname: self.hostname)
|
|
100
|
+
end
|
|
101
|
+
|
|
161
102
|
# context.setup
|
|
162
103
|
# context.freeze
|
|
163
104
|
|
|
@@ -246,11 +187,12 @@ module IO::Endpoint
|
|
|
246
187
|
|
|
247
188
|
# @parameter arguments
|
|
248
189
|
# @parameter ssl_context [OpenSSL::SSL::SSLContext, nil]
|
|
190
|
+
# @parameter tls_configuration [TLS::Configuration, nil]
|
|
249
191
|
# @parameter hostname [String, nil]
|
|
250
192
|
# @parameter options keyword arguments passed through to {Endpoint.tcp}
|
|
251
193
|
#
|
|
252
194
|
# @returns [SSLEndpoint]
|
|
253
|
-
def self.ssl(*arguments, ssl_context: nil, hostname: nil, **options)
|
|
254
|
-
SSLEndpoint.new(self.tcp(*arguments, **options), ssl_context: ssl_context, hostname: hostname)
|
|
195
|
+
def self.ssl(*arguments, ssl_context: nil, tls_configuration: nil, hostname: nil, **options)
|
|
196
|
+
SSLEndpoint.new(self.tcp(*arguments, **options), ssl_context: ssl_context, tls_configuration: tls_configuration, hostname: hostname)
|
|
255
197
|
end
|
|
256
198
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
module IO::Endpoint
|
|
7
|
+
# @namespace
|
|
8
|
+
module TLS
|
|
9
|
+
# Utilities for parsing PEM-encoded certificates.
|
|
10
|
+
module Certificates
|
|
11
|
+
# Matches individual certificates in a PEM bundle.
|
|
12
|
+
CERTIFICATE_PATTERN = /-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/m
|
|
13
|
+
private_constant :CERTIFICATE_PATTERN
|
|
14
|
+
|
|
15
|
+
# Parse a PEM-encoded certificate bundle into individual certificates.
|
|
16
|
+
# @parameter certificate_bundle [String] One or more certificates encoded as PEM.
|
|
17
|
+
# @returns [Array(String)] The individual PEM-encoded certificates in their original order.
|
|
18
|
+
# @raises [ArgumentError] If the bundle does not contain any certificates.
|
|
19
|
+
# @raises [TypeError] If the bundle is not a string.
|
|
20
|
+
def self.parse(certificate_bundle)
|
|
21
|
+
unless certificate_bundle.is_a?(String)
|
|
22
|
+
raise TypeError, "The certificate bundle must be provided as a string!"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
certificates = certificate_bundle.scan(CERTIFICATE_PATTERN)
|
|
26
|
+
|
|
27
|
+
unless certificates.any?
|
|
28
|
+
raise ArgumentError, "The certificate bundle does not contain any certificates!"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
return certificates
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "trust_store"
|
|
7
|
+
|
|
8
|
+
module IO::Endpoint
|
|
9
|
+
# @namespace
|
|
10
|
+
module TLS
|
|
11
|
+
# Represents transport-neutral TLS certificate and verification configuration.
|
|
12
|
+
class Configuration
|
|
13
|
+
# Initialize a TLS configuration from PEM-encoded certificate and private key material.
|
|
14
|
+
# @parameter trust_store [TrustStore | Nil] The trusted certificate sources.
|
|
15
|
+
# @parameter certificate_chain [Array(String) | Nil] The ordered local certificate chain encoded as individual PEM strings, with the leaf certificate followed by any intermediates.
|
|
16
|
+
# @parameter private_key [String | Nil] The private key encoded as PEM.
|
|
17
|
+
# @parameter verification [Symbol | Nil] The peer verification policy: `:none`, `:peer`, or `:required`. When omitted, `:peer` is used if a trust store is provided.
|
|
18
|
+
# @raises [ArgumentError] If the certificate chain and private key are not provided together, or the verification policy is invalid.
|
|
19
|
+
# @raises [TypeError] If the certificate chain or private key uses an unsupported representation.
|
|
20
|
+
def initialize(trust_store: nil, certificate_chain: nil, private_key: nil, verification: nil)
|
|
21
|
+
if certificate_chain
|
|
22
|
+
unless certificate_chain.is_a?(Array) && certificate_chain.all?{|certificate| certificate.is_a?(String)}
|
|
23
|
+
raise TypeError, "The certificate chain must be provided as an array of strings!"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
unless certificate_chain.any?
|
|
27
|
+
raise ArgumentError, "The certificate chain must contain at least one certificate!"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
unless private_key.nil? || private_key.is_a?(String)
|
|
32
|
+
raise TypeError, "The private key must be provided as a string!"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
if certificate_chain.nil? != private_key.nil?
|
|
36
|
+
raise ArgumentError, "The certificate chain and private key must be provided together!"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
verification = :peer if verification.nil? && trust_store
|
|
40
|
+
unless [nil, :none, :peer, :required].include?(verification)
|
|
41
|
+
raise ArgumentError, "Unsupported verification policy: #{verification.inspect}!"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@trust_store = trust_store
|
|
45
|
+
@certificate_chain = certificate_chain
|
|
46
|
+
@private_key = private_key
|
|
47
|
+
@verification = verification
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @attribute [TrustStore | Nil] The trusted certificate sources.
|
|
51
|
+
attr :trust_store
|
|
52
|
+
|
|
53
|
+
# @attribute [Array(String) | Nil] The ordered local certificate chain encoded as individual PEM strings, with the leaf certificate followed by any intermediates.
|
|
54
|
+
attr :certificate_chain
|
|
55
|
+
|
|
56
|
+
# @attribute [String | Nil] The private key encoded as PEM.
|
|
57
|
+
attr :private_key
|
|
58
|
+
|
|
59
|
+
# @attribute [Symbol | Nil] The peer verification policy.
|
|
60
|
+
attr :verification
|
|
61
|
+
|
|
62
|
+
# Whether peer certificates should be verified.
|
|
63
|
+
# @returns [Boolean] Whether peer verification is enabled.
|
|
64
|
+
def verify_peer?
|
|
65
|
+
return @verification == :peer || @verification == :required
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Get a representation of the configuration without exposing certificate or private key material.
|
|
69
|
+
# @returns [String] A redacted representation of the configuration.
|
|
70
|
+
def inspect
|
|
71
|
+
attributes = {
|
|
72
|
+
trust_store: !@trust_store.nil?,
|
|
73
|
+
certificate_chain: !@certificate_chain.nil?,
|
|
74
|
+
private_key: !@private_key.nil?,
|
|
75
|
+
verification: @verification,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return "\#<#{self.class} #{attributes.inspect}>"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "trust_store"
|
|
7
|
+
require_relative "configuration"
|
|
8
|
+
|
|
9
|
+
require "openssl"
|
|
10
|
+
|
|
11
|
+
module IO::Endpoint
|
|
12
|
+
module TLS
|
|
13
|
+
# Provides OpenSSL compilation for transport-neutral TLS configuration.
|
|
14
|
+
module OpenSSL
|
|
15
|
+
# Build an OpenSSL certificate store from transport-neutral trusted certificate configuration.
|
|
16
|
+
# @parameter trust_store [TrustStore] The trusted certificate sources.
|
|
17
|
+
# @returns [OpenSSL::X509::Store] The configured OpenSSL certificate store.
|
|
18
|
+
def self.build_certificate_store(trust_store)
|
|
19
|
+
::OpenSSL::X509::Store.new.tap do |store|
|
|
20
|
+
store.set_default_paths if trust_store.system_certificates?
|
|
21
|
+
|
|
22
|
+
trust_store.certificates.each do |certificate_pem|
|
|
23
|
+
store.add_cert(::OpenSSL::X509::Certificate.new(certificate_pem))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Apply transport-neutral TLS configuration to an OpenSSL context.
|
|
29
|
+
# @parameter context [OpenSSL::SSL::SSLContext] The OpenSSL context to configure.
|
|
30
|
+
# @parameter configuration [Configuration] The transport-neutral TLS configuration.
|
|
31
|
+
# @parameter hostname [String | Nil] The hostname to verify for client connections.
|
|
32
|
+
# @returns [OpenSSL::SSL::SSLContext] The configured OpenSSL context.
|
|
33
|
+
def self.apply(context, configuration, hostname: nil)
|
|
34
|
+
if trust_store = configuration.trust_store
|
|
35
|
+
context.cert_store = build_certificate_store(trust_store)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if certificate_chain = configuration.certificate_chain
|
|
39
|
+
certificates = certificate_chain.map do |certificate|
|
|
40
|
+
::OpenSSL::X509::Certificate.new(certificate)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
context.cert = certificates.shift
|
|
44
|
+
context.extra_chain_cert = certificates
|
|
45
|
+
context.key = ::OpenSSL::PKey.read(configuration.private_key)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
case configuration.verification
|
|
49
|
+
when :none
|
|
50
|
+
context.verify_mode = ::OpenSSL::SSL::VERIFY_NONE
|
|
51
|
+
context.verify_hostname = false
|
|
52
|
+
when :peer
|
|
53
|
+
context.verify_mode = ::OpenSSL::SSL::VERIFY_PEER
|
|
54
|
+
when :required
|
|
55
|
+
context.verify_mode = ::OpenSSL::SSL::VERIFY_PEER | ::OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if hostname && configuration.verify_peer?
|
|
59
|
+
context.verify_hostname = true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return context
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Samuel Williams.
|
|
5
|
+
|
|
6
|
+
require_relative "certificates"
|
|
7
|
+
|
|
8
|
+
module IO::Endpoint
|
|
9
|
+
# @namespace
|
|
10
|
+
module TLS
|
|
11
|
+
# Represents transport-neutral trusted certificate sources.
|
|
12
|
+
class TrustStore
|
|
13
|
+
# Load a PEM-encoded certificate bundle from the given path.
|
|
14
|
+
# @parameter path [String | Interface(:to_path)] The path to the certificate bundle.
|
|
15
|
+
# @parameter options [Hash] Options forwarded to {.parse}.
|
|
16
|
+
# @returns [TrustStore] The loaded trust store.
|
|
17
|
+
def self.load(path, **options)
|
|
18
|
+
return parse(File.read(path), **options)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Parse a PEM-encoded certificate bundle into a trust store.
|
|
22
|
+
# @parameter certificate_bundle [String] One or more trusted certificates encoded as PEM.
|
|
23
|
+
# @parameter system_certificates [Boolean] Whether system-provided trusted certificates should be included.
|
|
24
|
+
# @returns [TrustStore] The parsed trust store.
|
|
25
|
+
# @raises [ArgumentError] If the bundle does not contain any certificates.
|
|
26
|
+
# @raises [TypeError] If the bundle is not a string.
|
|
27
|
+
def self.parse(certificate_bundle, system_certificates: false)
|
|
28
|
+
return self.new(certificates: Certificates.parse(certificate_bundle), system_certificates: system_certificates)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Initialize a trust store from PEM-encoded trusted certificates.
|
|
32
|
+
# @parameter certificates [Array(String)] The trusted certificates encoded as PEM.
|
|
33
|
+
# @parameter system_certificates [Boolean] Whether system-provided trusted certificates should be included.
|
|
34
|
+
# @raises [ArgumentError] If no source of trusted certificates is specified.
|
|
35
|
+
# @raises [TypeError] If certificates are not provided as strings.
|
|
36
|
+
def initialize(certificates: [], system_certificates: false)
|
|
37
|
+
unless certificates.is_a?(Array) && certificates.all?{|certificate| certificate.is_a?(String)}
|
|
38
|
+
raise TypeError, "Certificates must be provided as an array of strings!"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
unless certificates.any? || system_certificates
|
|
42
|
+
raise ArgumentError, "At least one source of trusted certificates must be specified!"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@certificates = certificates
|
|
46
|
+
@system_certificates = system_certificates
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @attribute [Array(String)] The individual trusted certificates encoded as PEM.
|
|
50
|
+
attr :certificates
|
|
51
|
+
|
|
52
|
+
# Whether system-provided trusted certificates should be included.
|
|
53
|
+
# @returns [Boolean] `true` if system-provided trusted certificates should be included.
|
|
54
|
+
def system_certificates?
|
|
55
|
+
@system_certificates
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get a representation of the trust store without exposing certificate material.
|
|
59
|
+
# @returns [String] A redacted representation of the trust store.
|
|
60
|
+
def inspect
|
|
61
|
+
attributes = {
|
|
62
|
+
certificates: @certificates.size,
|
|
63
|
+
system_certificates: @system_certificates,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return "\#<#{self.class} #{attributes.inspect}>"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
data/lib/io/endpoint/version.rb
CHANGED
data/lib/io/endpoint.rb
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
require_relative "endpoint/version"
|
|
7
7
|
require_relative "endpoint/generic"
|
|
8
8
|
require_relative "endpoint/shared_endpoint"
|
|
9
|
+
require_relative "endpoint/tls/certificates"
|
|
10
|
+
require_relative "endpoint/tls/trust_store"
|
|
11
|
+
require_relative "endpoint/tls/configuration"
|
|
9
12
|
|
|
10
13
|
# Represents a collection of endpoint classes for network I/O operations.
|
|
11
14
|
module IO::Endpoint
|
data/readme.md
CHANGED
|
@@ -16,6 +16,12 @@ Please see the [project documentation](https://socketry.github.io/io-endpoint) f
|
|
|
16
16
|
|
|
17
17
|
Please see the [project releases](https://socketry.github.io/io-endpointreleases/index) for all releases.
|
|
18
18
|
|
|
19
|
+
### v0.18.0
|
|
20
|
+
|
|
21
|
+
- The `openssl` gem 3.3.0 or newer is now required.
|
|
22
|
+
- Added transport-neutral TLS trust stores, certificate chains and configuration, suitable for future QUIC integration.
|
|
23
|
+
- Added OpenSSL conversion and SSL endpoint integration, including hostname verification and mutual TLS.
|
|
24
|
+
|
|
19
25
|
### v0.17.2
|
|
20
26
|
|
|
21
27
|
- When the unix path is bigger than what can fit into `struct sockaddr_un`, a shorter temporary path will be used instead and a symlink created at the original path.
|
|
@@ -54,10 +60,6 @@ Please see the [project releases](https://socketry.github.io/io-endpointreleases
|
|
|
54
60
|
|
|
55
61
|
- Fixed state leak between iterations of the accept loop.
|
|
56
62
|
|
|
57
|
-
### v0.13.0
|
|
58
|
-
|
|
59
|
-
- Propagate options assigned to composite endpoint to nested endpoints.
|
|
60
|
-
|
|
61
63
|
## See Also
|
|
62
64
|
|
|
63
65
|
- [async-io](https://github.com/socketry/async-io) — Where this implementation originally came from.
|
|
@@ -66,11 +68,27 @@ Please see the [project releases](https://socketry.github.io/io-endpointreleases
|
|
|
66
68
|
|
|
67
69
|
We welcome contributions to this project.
|
|
68
70
|
|
|
69
|
-
1. Fork
|
|
71
|
+
1. Fork the repository.
|
|
70
72
|
2. Create your feature branch (`git checkout -b my-new-feature`).
|
|
71
|
-
3. Commit your changes (`git commit -am 'Add some feature'`).
|
|
73
|
+
3. Commit your changes (`git commit -am 'Add some feature.'`).
|
|
72
74
|
4. Push to the branch (`git push origin my-new-feature`).
|
|
73
|
-
5. Create new
|
|
75
|
+
5. Create a new pull request.
|
|
76
|
+
|
|
77
|
+
### Running Tests
|
|
78
|
+
|
|
79
|
+
To run the test suite:
|
|
80
|
+
|
|
81
|
+
``` bash
|
|
82
|
+
$ bundle exec sus
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Making Releases
|
|
86
|
+
|
|
87
|
+
To make a new release:
|
|
88
|
+
|
|
89
|
+
``` bash
|
|
90
|
+
$ bundle exec bake gem:release:patch # or minor or major
|
|
91
|
+
```
|
|
74
92
|
|
|
75
93
|
### Developer Certificate of Origin
|
|
76
94
|
|
data/releases.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Releases
|
|
2
2
|
|
|
3
|
+
## v0.18.0
|
|
4
|
+
|
|
5
|
+
- The `openssl` gem 3.3.0 or newer is now required.
|
|
6
|
+
- Added transport-neutral TLS trust stores, certificate chains and configuration, suitable for future QUIC integration.
|
|
7
|
+
- Added OpenSSL conversion and SSL endpoint integration, including hostname verification and mutual TLS.
|
|
8
|
+
|
|
3
9
|
## v0.17.2
|
|
4
10
|
|
|
5
11
|
- When the unix path is bigger than what can fit into `struct sockaddr_un`, a shorter temporary path will be used instead and a symlink created at the original path.
|
data.tar.gz.sig
CHANGED
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: io-endpoint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Samuel Williams
|
|
@@ -38,7 +38,21 @@ cert_chain:
|
|
|
38
38
|
voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
|
|
39
39
|
-----END CERTIFICATE-----
|
|
40
40
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
41
|
-
dependencies:
|
|
41
|
+
dependencies:
|
|
42
|
+
- !ruby/object:Gem::Dependency
|
|
43
|
+
name: openssl
|
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: 3.3.0
|
|
49
|
+
type: :runtime
|
|
50
|
+
prerelease: false
|
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
52
|
+
requirements:
|
|
53
|
+
- - ">="
|
|
54
|
+
- !ruby/object:Gem::Version
|
|
55
|
+
version: 3.3.0
|
|
42
56
|
executables: []
|
|
43
57
|
extensions: []
|
|
44
58
|
extra_rdoc_files: []
|
|
@@ -57,6 +71,10 @@ files:
|
|
|
57
71
|
- lib/io/endpoint/shared_endpoint.rb
|
|
58
72
|
- lib/io/endpoint/socket_endpoint.rb
|
|
59
73
|
- lib/io/endpoint/ssl_endpoint.rb
|
|
74
|
+
- lib/io/endpoint/tls/certificates.rb
|
|
75
|
+
- lib/io/endpoint/tls/configuration.rb
|
|
76
|
+
- lib/io/endpoint/tls/openssl.rb
|
|
77
|
+
- lib/io/endpoint/tls/trust_store.rb
|
|
60
78
|
- lib/io/endpoint/unix_endpoint.rb
|
|
61
79
|
- lib/io/endpoint/version.rb
|
|
62
80
|
- lib/io/endpoint/wrapper.rb
|
|
@@ -67,6 +85,8 @@ homepage: https://github.com/socketry/io-endpoint
|
|
|
67
85
|
licenses:
|
|
68
86
|
- MIT
|
|
69
87
|
metadata:
|
|
88
|
+
bug_tracker_uri: https://github.com/socketry/io-endpoint/issues
|
|
89
|
+
changelog_uri: https://github.com/socketry/io-endpoint/blob/main/releases.md
|
|
70
90
|
documentation_uri: https://socketry.github.io/io-endpoint
|
|
71
91
|
source_code_uri: https://github.com/socketry/io-endpoint.git
|
|
72
92
|
rdoc_options: []
|
|
@@ -76,14 +96,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
76
96
|
requirements:
|
|
77
97
|
- - ">="
|
|
78
98
|
- !ruby/object:Gem::Version
|
|
79
|
-
version: '3.
|
|
99
|
+
version: '3.3'
|
|
80
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
101
|
requirements:
|
|
82
102
|
- - ">="
|
|
83
103
|
- !ruby/object:Gem::Version
|
|
84
104
|
version: '0'
|
|
85
105
|
requirements: []
|
|
86
|
-
rubygems_version: 4.0.
|
|
106
|
+
rubygems_version: 4.0.10
|
|
87
107
|
specification_version: 4
|
|
88
108
|
summary: Provides a separation of concerns interface for IO endpoints.
|
|
89
109
|
test_files: []
|
metadata.gz.sig
CHANGED
|
Binary file
|