Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SSL] Add ability to set verification flags #2490

Merged
merged 1 commit into from Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions History.md
Expand Up @@ -2,6 +2,7 @@

* Features
* Your feature goes here <Most recent on the top, like GitHub> (#Github Number)
* Add ability to set OpenSSL verification flags (MRI only) ([#2490])
* Uses `flush` after writing messages to avoid mutating $stdout and $stderr using `sync=true` ([#2486])
* Fail build if compiling extensions raises warnings on GH Actions ([#1953])
* Add MAKE_WARNINGS_INTO_ERRORS environment variable to toggle whether a build should treat all warnings into errors or not ([#1953])
Expand Down
17 changes: 17 additions & 0 deletions README.md
Expand Up @@ -212,6 +212,23 @@ Disable TLS v1 with the `no_tlsv1` option:
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&no_tlsv1=true'
```

#### Controlling OpenSSL Verification Flags

To enable verification flags offered by OpenSSL, use `verification_flags` (not available for JRuby):

```
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&verification_flags=PARTIAL_CHAIN'
```

You can also set multiple verification flags (by separating them with coma):

```
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert&verification_flags=PARTIAL_CHAIN,CRL_CHECK'
```

List of available flags: `USE_CHECK_TIME`, `CRL_CHECK`, `CRL_CHECK_ALL`, `IGNORE_CRITICAL`, `X509_STRICT`, `ALLOW_PROXY_CERTS`, `POLICY_CHECK`, `EXPLICIT_POLICY`, `INHIBIT_ANY`, `INHIBIT_MAP`, `NOTIFY_POLICY`, `EXTENDED_CRL_SUPPORT`, `USE_DELTAS`, `CHECK_SS_SIGNATURE`, `TRUSTED_FIRST`, `SUITEB_128_LOS_ONLY`, `SUITEB_192_LOS`, `SUITEB_128_LOS`, `PARTIAL_CHAIN`, `NO_ALT_CHAINS`, `NO_CHECK_TIME`
(see https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_hostflags.html#VERIFICATION-FLAGS).

### Control/Status Server

Puma has a built-in status and control app that can be used to query and control Puma.
Expand Down
13 changes: 11 additions & 2 deletions ext/puma_http11/mini_ssl.c
Expand Up @@ -152,8 +152,8 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
int min;
#endif
int ssl_options;
ID sym_key, sym_cert, sym_ca, sym_verify_mode, sym_ssl_cipher_filter, sym_no_tlsv1, sym_no_tlsv1_1;
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1;
ID sym_key, sym_cert, sym_ca, sym_verify_mode, sym_ssl_cipher_filter, sym_no_tlsv1, sym_no_tlsv1_1, sym_verification_flags;
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1, verification_flags;
DH *dh;

#if OPENSSL_VERSION_NUMBER < 0x10002000L
Expand Down Expand Up @@ -197,6 +197,15 @@ VALUE engine_init_server(VALUE self, VALUE mini_ssl_ctx) {
SSL_CTX_use_certificate_chain_file(ctx, RSTRING_PTR(cert));
SSL_CTX_use_PrivateKey_file(ctx, RSTRING_PTR(key), SSL_FILETYPE_PEM);

sym_verification_flags = rb_intern("verification_flags");
verification_flags = rb_funcall(mini_ssl_ctx, sym_verification_flags, 0);

if (!NIL_P(verification_flags)) {
X509_VERIFY_PARAM *param = SSL_CTX_get0_param(ctx);
X509_VERIFY_PARAM_set_flags(param, NUM2INT(verification_flags));
SSL_CTX_set1_param(ctx, param);
}

if (!NIL_P(ca)) {
StringValue(ca);
SSL_CTX_load_verify_locations(ctx, RSTRING_PTR(ca), NULL);
Expand Down
9 changes: 8 additions & 1 deletion lib/puma/dsl.rb
Expand Up @@ -51,14 +51,20 @@ def self.ssl_bind_str(host, port, opts)
if defined?(JRUBY_VERSION)
ssl_cipher_list = opts[:ssl_cipher_list] ?
"&ssl_cipher_list=#{opts[:ssl_cipher_list]}" : nil

keystore_additions = "keystore=#{opts[:keystore]}&keystore-pass=#{opts[:keystore_pass]}"

"ssl://#{host}:#{port}?#{keystore_additions}#{ssl_cipher_list}" \
"&verify_mode=#{verify}#{tls_str}#{ca_additions}"
else
ssl_cipher_filter = opts[:ssl_cipher_filter] ?
"&ssl_cipher_filter=#{opts[:ssl_cipher_filter]}" : nil

v_flags = (ary = opts[:verification_flags]) ?
"&verification_flags=#{Array(ary).join ','}" : nil

"ssl://#{host}:#{port}?cert=#{opts[:cert]}&key=#{opts[:key]}" \
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}"
"#{ssl_cipher_filter}&verify_mode=#{verify}#{tls_str}#{ca_additions}#{v_flags}"
end
end

Expand Down Expand Up @@ -429,6 +435,7 @@ def threads(min, max)
# key: path_to_key,
# ssl_cipher_filter: cipher_filter, # optional
# verify_mode: verify_mode, # default 'none'
# verification_flags: flags, # optional, not supported by JRuby
# }
# @example For JRuby, two keys are required: keystore & keystore_pass.
# ssl_bind '127.0.0.1', '9292', {
Expand Down
27 changes: 27 additions & 0 deletions lib/puma/minissl.rb
Expand Up @@ -245,6 +245,7 @@ def check
attr_reader :cert
attr_reader :ca
attr_accessor :ssl_cipher_filter
attr_accessor :verification_flags

def key=(key)
raise ArgumentError, "No such key file '#{key}'" unless File.exist? key
Expand Down Expand Up @@ -287,6 +288,32 @@ def no_tlsv1_1=(tlsv1_1)
VERIFY_PEER = 1
VERIFY_FAIL_IF_NO_PEER_CERT = 2

# https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
# /* Certificate verify flags */
VERIFICATION_FLAGS = {
"USE_CHECK_TIME" => 0x2,
"CRL_CHECK" => 0x4,
"CRL_CHECK_ALL" => 0x8,
"IGNORE_CRITICAL" => 0x10,
"X509_STRICT" => 0x20,
"ALLOW_PROXY_CERTS" => 0x40,
"POLICY_CHECK" => 0x80,
"EXPLICIT_POLICY" => 0x100,
"INHIBIT_ANY" => 0x200,
"INHIBIT_MAP" => 0x400,
"NOTIFY_POLICY" => 0x800,
"EXTENDED_CRL_SUPPORT" => 0x1000,
"USE_DELTAS" => 0x2000,
"CHECK_SS_SIGNATURE" => 0x4000,
"TRUSTED_FIRST" => 0x8000,
"SUITEB_128_LOS_ONLY" => 0x10000,
"SUITEB_192_LOS" => 0x20000,
"SUITEB_128_LOS" => 0x30000,
"PARTIAL_CHAIN" => 0x80000,
"NO_ALT_CHAINS" => 0x100000,
"NO_CHECK_TIME" => 0x200000
}.freeze

class Server
def initialize(socket, ctx)
@socket = socket
Expand Down
6 changes: 6 additions & 0 deletions lib/puma/minissl/context_builder.rb
Expand Up @@ -62,6 +62,12 @@ def context
end
end

if params['verification_flags']
ctx.verification_flags = params['verification_flags'].split(',').
map { |flag| MiniSSL::VERIFICATION_FLAGS.fetch(flag) }.
inject { |sum, flag| sum ? sum | flag : flag }
end

ctx
end

Expand Down
20 changes: 20 additions & 0 deletions test/test_binder.rb
Expand Up @@ -453,4 +453,24 @@ def test_binder_parses_ssl_cipher_filter

assert_equal ssl_cipher_filter, ssl_context_for_binder.ssl_cipher_filter
end

def test_binder_parses_ssl_verification_flags_one
skip 'No ssl support' unless ::Puma::HAS_SSL

input = "&verification_flags=TRUSTED_FIRST"

@binder.parse ["ssl://0.0.0.0?#{ssl_query}#{input}"], @events

assert_equal 0x8000, ssl_context_for_binder.verification_flags
end

def test_binder_parses_ssl_verification_flags_multiple
skip 'No ssl support' unless ::Puma::HAS_SSL

input = "&verification_flags=TRUSTED_FIRST,NO_CHECK_TIME"

@binder.parse ["ssl://0.0.0.0?#{ssl_query}#{input}"], @events

assert_equal 0x8000 | 0x200000, ssl_context_for_binder.verification_flags
end
end unless ::Puma::IS_JRUBY
18 changes: 18 additions & 0 deletions test/test_config.rb
Expand Up @@ -141,6 +141,24 @@ def test_ssl_bind_with_cipher_filter
assert ssl_binding.include?("&ssl_cipher_filter=#{cipher_filter}")
end

def test_ssl_bind_with_verification_flags
skip_on :jruby
skip 'No ssl support' unless ::Puma::HAS_SSL

conf = Puma::Configuration.new do |c|
c.ssl_bind "0.0.0.0", "9292", {
cert: "cert",
key: "key",
verification_flags: ["TRUSTED_FIRST", "NO_CHECK_TIME"]
}
end

conf.load

ssl_binding = conf.options[:binds].first
assert ssl_binding.include?("&verification_flags=TRUSTED_FIRST,NO_CHECK_TIME")
end

def test_ssl_bind_with_ca
skip 'No ssl support' unless ::Puma::HAS_SSL
conf = Puma::Configuration.new do |c|
Expand Down