Skip to content

Commit

Permalink
[SSL] Add ability to set verification flags
Browse files Browse the repository at this point in the history
Any number of verification flags supported by OpenSSL can be set (
https://www.openssl.org/docs/manmaster/man3/X509_VERIFY_PARAM_set_hostflags.html#VERIFICATION-FLAGS
  • Loading branch information
Piotr Boniecki committed Dec 7, 2020
1 parent b364a5d commit 555e4a8
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 3 deletions.
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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

0 comments on commit 555e4a8

Please sign in to comment.