From eec259aff3eb6a750c2a085e363fd6acf9a5a3af Mon Sep 17 00:00:00 2001 From: Keren Jin Date: Mon, 23 Jul 2018 17:59:02 -0700 Subject: [PATCH] Support custom TLS handshake verify to origin server --- iocore/net/P_SSLNetVConnection.h | 11 ++ iocore/net/SSLNetVConnection.cc | 17 +++ proxy/api/ts/ts.h | 3 + src/traffic_server/InkAPI.cc | 40 ++++-- tests/README.md | 2 +- tests/gold_tests/tls/ssl/ca.key | 27 ++++ tests/gold_tests/tls/ssl/ca.pem | 20 +++ tests/gold_tests/tls/ssl/server.ats.test.key | 27 ++++ tests/gold_tests/tls/ssl/server.ats.test.pem | 20 +++ tests/gold_tests/tls/ssl_verify.test.py | 88 ++++++++++++ tests/tools/plugins/ssl_verify_remap.cc | 141 +++++++++++++++++++ 11 files changed, 385 insertions(+), 11 deletions(-) create mode 100644 tests/gold_tests/tls/ssl/ca.key create mode 100644 tests/gold_tests/tls/ssl/ca.pem create mode 100644 tests/gold_tests/tls/ssl/server.ats.test.key create mode 100644 tests/gold_tests/tls/ssl/server.ats.test.pem create mode 100644 tests/gold_tests/tls/ssl_verify.test.py create mode 100644 tests/tools/plugins/ssl_verify_remap.cc diff --git a/iocore/net/P_SSLNetVConnection.h b/iocore/net/P_SSLNetVConnection.h index 271b718b6e3..ea0b8a7fbda 100644 --- a/iocore/net/P_SSLNetVConnection.h +++ b/iocore/net/P_SSLNetVConnection.h @@ -303,6 +303,13 @@ class SSLNetVConnection : public UnixNetVConnection */ int populate(Connection &con, Continuation *c, void *arg) override; + void + setSSLVerifyCallback(void *callback, void *args) + { + sslVerifyCallback = callback; + sslVerifyCallbackArgs = args; + } + SSL *ssl = nullptr; ink_hrtime sslHandshakeBeginTime = 0; ink_hrtime sslHandshakeEndTime = 0; @@ -352,9 +359,13 @@ class SSLNetVConnection : public UnixNetVConnection bool sslTrace = false; bool SNIMapping = false; int64_t redoWriteSize = 0; + void *sslVerifyCallback = nullptr; + void *sslVerifyCallbackArgs = nullptr; #ifdef SSL_MODE_ASYNC EventIO signalep; #endif + + static int sslVerifyCallbackProxy(int preverify_ok, X509_STORE_CTX *x509_ctx); }; typedef int (SSLNetVConnection::*SSLNetVConnHandler)(int, void *); diff --git a/iocore/net/SSLNetVConnection.cc b/iocore/net/SSLNetVConnection.cc index 6cb133daeec..216d741b5c0 100644 --- a/iocore/net/SSLNetVConnection.cc +++ b/iocore/net/SSLNetVConnection.cc @@ -1036,6 +1036,10 @@ SSLNetVConnection::sslStartHandShake(int event, int &err) } } + if (this->sslVerifyCallback != nullptr) { + SSL_set_verify(this->ssl, SSL_VERIFY_PEER, sslVerifyCallbackProxy); + } + return sslClientHandShakeEvent(err); default: @@ -1710,6 +1714,19 @@ SSLNetVConnection::populate(Connection &con, Continuation *c, void *arg) return EVENT_DONE; } +int +SSLNetVConnection::sslVerifyCallbackProxy(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + SSL *ssl = static_cast(X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); + SSLNetVConnection *netvc = SSLNetVCAccess(ssl); + if (netvc == nullptr) { + return preverify_ok; + } else { + return reinterpret_cast(netvc->sslVerifyCallback)(netvc->sslVerifyCallbackArgs, + preverify_ok, x509_ctx); + } +} + std::string_view SSLNetVConnection::map_tls_protocol_to_tag(const char *proto_string) const { diff --git a/proxy/api/ts/ts.h b/proxy/api/ts/ts.h index a77155c5d89..95427425a81 100644 --- a/proxy/api/ts/ts.h +++ b/proxy/api/ts/ts.h @@ -1508,6 +1508,7 @@ tsapi void TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg); tsapi void *TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx); tsapi void TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void *arg); tsapi void *TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx); +tsapi bool TSVConnVerifyCallbackSet(TSVConn connp, void *callback, void *args); tsapi void TSVConnArgSet(TSVConn connp, int arg_idx, void *arg); tsapi void *TSVConnArgGet(TSVConn connp, int arg_idx); @@ -2450,6 +2451,8 @@ tsapi TSReturnCode TSRemapToUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp); */ tsapi TSIOBufferReader TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp); +tsapi TSVConn TSHttpTxnServerVConnGet(TSHttpTxn txnp); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/src/traffic_server/InkAPI.cc b/src/traffic_server/InkAPI.cc index 6fef4fc6bd0..56ce72c6cba 100644 --- a/src/traffic_server/InkAPI.cc +++ b/src/traffic_server/InkAPI.cc @@ -5410,16 +5410,7 @@ TSHttpTxnIncomingAddrGet(TSHttpTxn txnp) sockaddr const * TSHttpTxnOutgoingAddrGet(TSHttpTxn txnp) { - sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); - - HttpSM *sm = reinterpret_cast(txnp); - - HttpServerSession *ssn = sm->get_server_session(); - if (ssn == nullptr) { - return nullptr; - } - - NetVConnection *vc = ssn->get_netvc(); + NetVConnection *vc = reinterpret_cast(TSHttpTxnServerVConnGet(txnp)); if (vc == nullptr) { return nullptr; } @@ -9657,3 +9648,32 @@ TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp) HttpSM *sm = (HttpSM *)txnp; return (TSIOBufferReader)sm->get_postbuf_clone_reader(); } + +bool +TSVConnVerifyCallbackSet(TSVConn connp, void *callback, void *args) +{ + sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS); + + NetVConnection *net_vc = reinterpret_cast(connp); + SSLNetVConnection *ssl_vc = dynamic_cast(net_vc); + if (ssl_vc != nullptr) { + ssl_vc->setSSLVerifyCallback(callback, args); + return true; + } + return false; +} + +TSVConn +TSHttpTxnServerVConnGet(TSHttpTxn txnp) +{ + sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS); + + HttpSM *sm = reinterpret_cast(txnp); + + HttpServerSession *ssn = sm->get_server_session(); + if (ssn == nullptr) { + return nullptr; + } + + return reinterpret_cast(ssn->get_netvc()); +} diff --git a/tests/README.md b/tests/README.md index 619c0c04249..a7b3414b88f 100644 --- a/tests/README.md +++ b/tests/README.md @@ -67,7 +67,7 @@ tr.Processes.Default.Env=ts.Env These are the current variable that are define dynamically port - the ipv4 port to listen on -portv6 - the ipv4 port to listen on +portv6 - the ipv6 port to listen on manager_port - the manager port used. This is set even is select_port is False admin_port - the admin port used. This is set even is select_port is False diff --git a/tests/gold_tests/tls/ssl/ca.key b/tests/gold_tests/tls/ssl/ca.key new file mode 100644 index 00000000000..66066609e5d --- /dev/null +++ b/tests/gold_tests/tls/ssl/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAvf/vbv7bSVXx3xL6JghGxEFh4BenkFT3m3LIHwYziFDEp3Ae +A4Q83fja8sdeEc/HA0vYxgjgEwMX+3cDvDIwfcJD7QaZ7ddhaazLfsGjqN76sOjy +Pt5Y+m5++MvSy8Q8T39YNwvdIqorUElx+ttoUstEUXjFHCgHHOLNHdeHFn+R+gIb +gSZgkGYe6xLGs8UD1cTnEjQOZ17jc3Y9grYx1DJ0heAARFZVyOd9k13RbV1UV2NY +c2xI1i+a5zYvcJpGY921cb8zowCIYD4rg6Ef97oU6RgCzST57xRicOwtu+78Zv28 +qfJa582zIoH+wFvOMR8G4G+03mVt2uyHM+4giwIDAQABAoIBABXWEEKFb9Zh1kZ4 ++97bHKb5jLIghdWErmBjNLS8FbgY8ED+FbT5wbURILi+SQVrB8hNM0CvTHFi7aKs +7vT2H7SUWxImrBI1qrBmd4y93H1QAyfJ3e1zyG4fvOADLNrWVsgcLptE9clCHXBA +wKTACqFZUyS56I1v3EFpn4FKf/AJzkEFxuICGLQZ+leR8pDA6hEuG9UE2uTI00Dp +oFdO9oVd4Z59MslZB3rGYZkE7rXB7n/LMyOUAfad2oAwzIY6P6KMnO6/6dOUu4GL +XEM7PS1Q6vmcl1iqQGln2RyYsRIuLYIBzsyzTPm44XQFRL2QzDfCGsWPDmlZQ1fu +1BThkiECgYEA+MyBmaUmdgfb79XkMyeygfXK5GGt/u/KVpKrIGMNOwlIs6N4p5Bo +eoq0BIcIDcDM/6tON3d4d+rEyIfbq4N88rJmM2CziUmtj8DTAh2/AScH3gQlrD10 +fxsvFRqP9SlcER1IJALypg3R+lDcRdl+do6BpNeyKqhQdZLXhQnYFPUCgYEAw3/A +ri/8SginDxTDpP5KbJhSwOr10SUQ8j17z3Zr/qqtslMmLniGGJCGhxJd7kWgF7Tc +m8qhYELXaRM+WUUeNx2tXPkWy0RM4mKM0RODNgtbVJUFVspd62ieqCOWn8ymx2PV +7P57OyN4jhlWklSpnpV7/QhFOOW+nbIwEAqf738CgYBFvfX6XtwYK/3QDjIo0NDu +MztLp8IBe7BJclW7/2gfC2wwxvs0ZXuUFjt+JWfxiwEsDhYf4EKJwuqkl8jRHEuz +MKULc+j/7AJWEKRVrIb1SL6g0qLI1HVKiBE7pjWCcLSZK48kYRspxyf8Nhx5DA2y +KCJZVglggcwEGUSGy4qh3QKBgQC/YSSiYF89qlLANLeBoV4CCOuZ52lUoFCCkyXM +5kS6PSNk2nkaLOo4yeKn/5bsVkRucKM175sivMxbFHeItFzr36WCymVB/060n3zy +f4OHOH7jwi5D1D804GnQd2ZQcOsjbdxR8J16OzMdCDjzbuXNVNDX12zMUjvT7EeY +rI38yQKBgQCTvk/2HjoJ1uLmdPo7BYyIqLuXlhHmpO23gI/U6scFJrLJatkrmU6s +eVOmd8N5Y7q9hBPKkKOPmZZ21KfTkN8nffNDPpRqH3fMpGHDogEN33A4r23uuJs3 +7+GwA182xqQl3JGOOWJkY8fK36154mawZRj06a9Ytseg2CDoHaWcsg== +-----END RSA PRIVATE KEY----- diff --git a/tests/gold_tests/tls/ssl/ca.pem b/tests/gold_tests/tls/ssl/ca.pem new file mode 100644 index 00000000000..a0fab86c5cb --- /dev/null +++ b/tests/gold_tests/tls/ssl/ca.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDOzCCAiOgAwIBAgIJAMj4prNPmwCCMA0GCSqGSIb3DQEBCwUAMDQxCzAJBgNV +BAYTAlhYMRAwDgYDVQQHDAdDQSBDaXR5MRMwEQYDVQQKDApDQSBDb21wYW55MB4X +DTE4MDcyNjAzNTUxMloXDTI4MDcyMzAzNTUxMlowNDELMAkGA1UEBhMCWFgxEDAO +BgNVBAcMB0NBIENpdHkxEzARBgNVBAoMCkNBIENvbXBhbnkwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC9/+9u/ttJVfHfEvomCEbEQWHgF6eQVPebcsgf +BjOIUMSncB4DhDzd+Nryx14Rz8cDS9jGCOATAxf7dwO8MjB9wkPtBpnt12FprMt+ +waOo3vqw6PI+3lj6bn74y9LLxDxPf1g3C90iqitQSXH622hSy0RReMUcKAcc4s0d +14cWf5H6AhuBJmCQZh7rEsazxQPVxOcSNA5nXuNzdj2CtjHUMnSF4ABEVlXI532T +XdFtXVRXY1hzbEjWL5rnNi9wmkZj3bVxvzOjAIhgPiuDoR/3uhTpGALNJPnvFGJw +7C277vxm/byp8lrnzbMigf7AW84xHwbgb7TeZW3a7Icz7iCLAgMBAAGjUDBOMB0G +A1UdDgQWBBRIzBkqtZ9A9gkzSahu45SNgKFJhTAfBgNVHSMEGDAWgBRIzBkqtZ9A +9gkzSahu45SNgKFJhTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCF +fJmwg4/mu7j81ULV0bB3GcswPNyfPlDEcTvHGyv1vZoW2NchdQdk7n6CWPs1bwmt +uuQB+gv3+UGcz/IXLgRKCNNVFO3p1Iy01VLvhvRMZbiM7tPkToBX1HFm+x3bu1rB +Rle5BMCS/Rv0C2IijBYIQ0tg2Cm8JrmZhW7PSIglxHwIiCZjYLHtMzqQHe2BtYjZ +nlZRLM1dG41urUeZR9XRTZW3MChGNYVnOCLItaefTaVORwNKz8XEa/N84ONI5RxN +yd0+DhghNiVrABBhK798aZGxGRIvRSjPPD0bCWXuSNAN87XE6SojBtGMVV4lUFX8 +tSdQSo1b5sYFqx0sK3jX +-----END CERTIFICATE----- diff --git a/tests/gold_tests/tls/ssl/server.ats.test.key b/tests/gold_tests/tls/ssl/server.ats.test.key new file mode 100644 index 00000000000..4f988724a9d --- /dev/null +++ b/tests/gold_tests/tls/ssl/server.ats.test.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAsbtcU6kGr7Oyt/b6aQwvMnblFTozICKuQOo0xHzEled1PKek +4SPLQj/vvFhjyGybkZKSZq1XCwtRoASU3wr5iROWxE6i0Ah334/h48y9I9SqaaRv +tYqceLW2Xf4OBMePF15Zh39G78mgwzYqEhkdy6VsgdpJ1uU35+B5lddLpqc07kA3 +jgSm0ynI6oM/h4E5d53VQm1YftZvN6gtDmhTD8/Cv5Gbgq050ToJbulL0q/S+JPM +90h7Mm5BmMOnG9RSAmlmfil1jozqc9PgSZcZsAfmAXVIeSxieeE57RHz/I0l+cO2 +823prtgYwTaY74/2GDhXNrkExnnfYKkFqhzKswIDAQABAoIBAFSGhqJDAKsPCy/y +haTv5oJZiRlPVIEz/StiNzY7IEMqkNcH46IvI7ueLKWmY84t2M6ipn2ucncOKwOv +Kh3pWkOWzV0PCN9nJ5trEkMEW+9udJ1JVtc/1KqTwOdyq7gaMDMs6+dE4LepbItq +dxekWkc/pRscdYF+AdiFKU3Z1mCq2cPtxnyw94VcFE8MIPbuxRc+RQRgyvos+BkR +GegTvrVZLy9XUZ1UPMycghQCJFtG59iBfeGKXWH0nW6TtAy31yglxnsz0jYfnnG4 +IsNEC8G5wSNRqxzIZmtRdH64L0xfU6nPMWATyA9G+aO5Mbtl9K82Y0CNbIdp14nd +cbk3+AECgYEA3BrMJJ5aSJ1UWmr6yKhEXF0nP+Wj4AHDk7OPuqoHInjxKLvdaPji +9zbeGz34c9p+pK4iZfldTBboJZ0D4J/oD6jttpA6pwRBML6so0d2l6gyt3y9IRqy +Yd2rp8fzGeu3BkctCtrsXwk6NMmA4vM33Zy/FZ/ZskhUGDhSOo0x2JECgYEAzreH +9roeHJTRioetZHLSL5VxGDtcAXdbgyeAos89psowTj+TmSL5WwoeToP2KbtikWJI +vfcEbMWxOBST3Eu6Bq+3wYgACyxRVr1g5Lvk+/Ah59cYO30RXkekwEVYTBkggWWO +Cc1LEXSbMF1FkUhfD19Cb2WV4lq+fjDXF6ybsQMCgYBraTBiVlycvxphHX/Q2Dy0 +DkmPJxO3x2SX+bKUV1EwA3E8faFpDrxEBCSTisRqVSAkYU4sEWr0LOGRKXSw85gd +vER1KSFWS/88OHCrJb6797r4TAlH1G1k0PZp5GvAJIWmrKlQeNGM90XOaN9a4XJL +1ahydxWXqTBQuXt3U1wV4QKBgEhFsHqZjaeOfEhDsWKaJlROmlDPiBZXJZ6QNBA4 +BqUGsabZ4X+705R50CjBSFJE2mxcAXSxJVHmOFSUFTIDI9Hso4E9TE6buwBpi7PM +4X0X8rK86BL8YhMlVzuFwx4v9sZmvCc/KEy9CPj60zS9KccVFgQeby3JbipuZvU2 ++uQDAoGAddPMlNKsBxEvjMzEHWzM0XaTYTG5dES92bb5B5PNabQDQZV+q5NkPmzT +OeRMYvragztATaKPdcp+xoKUoKzNi2+SlVBr20HFtl6hXBCYL/ipK/eZAYWoRqbe +LEsqpI8pAmvhoWeZ8Ux9Rw7d68126ydU4C40Tyvgtn18BJHUblE= +-----END RSA PRIVATE KEY----- diff --git a/tests/gold_tests/tls/ssl/server.ats.test.pem b/tests/gold_tests/tls/ssl/server.ats.test.pem new file mode 100644 index 00000000000..6b7db15b03b --- /dev/null +++ b/tests/gold_tests/tls/ssl/server.ats.test.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIJAO7Xyt9kZGa1MA0GCSqGSIb3DQEBCwUAMDQxCzAJBgNV +BAYTAlhYMRAwDgYDVQQHDAdDQSBDaXR5MRMwEQYDVQQKDApDQSBDb21wYW55MB4X +DTE4MDcyNjA1MTA0NloXDTI4MDcyMzA1MTA0NlowPDELMAkGA1UEBhMCWFgxFDAS +BgNVBAcMC1NlcnZlciBDaXR5MRcwFQYDVQQKDA5TZXJ2ZXIgQ29tcGFueTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALG7XFOpBq+zsrf2+mkMLzJ25RU6 +MyAirkDqNMR8xJXndTynpOEjy0I/77xYY8hsm5GSkmatVwsLUaAElN8K+YkTlsRO +otAId9+P4ePMvSPUqmmkb7WKnHi1tl3+DgTHjxdeWYd/Ru/JoMM2KhIZHculbIHa +SdblN+fgeZXXS6anNO5AN44EptMpyOqDP4eBOXed1UJtWH7WbzeoLQ5oUw/Pwr+R +m4KtOdE6CW7pS9Kv0viTzPdIezJuQZjDpxvUUgJpZn4pdY6M6nPT4EmXGbAH5gF1 +SHksYnnhOe0R8/yNJfnDtvNt6a7YGME2mO+P9hg4Vza5BMZ532CpBaocyrMCAwEA +AaNXMFUwHwYDVR0jBBgwFoAUSMwZKrWfQPYJM0mobuOUjYChSYUwCQYDVR0TBAIw +ADALBgNVHQ8EBAMCBPAwGgYDVR0RBBMwEYIPc2VydmVyLmF0cy50ZXN0MA0GCSqG +SIb3DQEBCwUAA4IBAQAgdSfs8wXGQPkiIm/BwRE/nr38RXSmh2I5zOOu5c54Y/0S +m2lDJI1ZX3U++asdydtODRFZ9mEK4hCcRCYstKVymAQHyXVnqoKll0YDzKXspL1a ++FmDE2B8yKp7punnAWJ48TyoxPmZRLEAojcnucIfbXzLbQT/jkJGclPeZ8VpZwcz +MtI1bh6xmB3PVFcGwOtOTFO6iU2hFBUG8CwLfJM4JJw+UM6AFe8c51SPPTtuAFJj +dULxa8/cfJa3x8CAfVhU13erM9p8euN+if7N5WJsNbHQ+eFN8A5EtAvboZd9vHrD +GLyvRjbrqjknHQldw+Oqs6+bHahYtRq+V9bWnvT/ +-----END CERTIFICATE----- diff --git a/tests/gold_tests/tls/ssl_verify.test.py b/tests/gold_tests/tls/ssl_verify.test.py new file mode 100644 index 00000000000..2d18c3409d5 --- /dev/null +++ b/tests/gold_tests/tls/ssl_verify.test.py @@ -0,0 +1,88 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# 'License'); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os + +Test.Summary = ''' +Test SSL verify callback by checking DNS record in Subject Alternative Name +''' + +Test.SkipUnless( + Condition.HasProgram('curl', 'Curl need to be installed on system for this test to work') +) + +SERVER_NAME_IN_CERT = 'server.ats.test' +# the synthetic server has different domain name, which mismatches the DNS record in the server cert +SYNTH_SERVER_NAME = 'synthetic.ats.test' + +ts = Test.MakeATSProcess('ts') +server = Test.MakeOriginServer("server", ssl=True, options={ + '--cert': '{0}/{1}.pem'.format(ts.Variables.SSLDir, SERVER_NAME_IN_CERT), + '--key': '{0}/{1}.key'.format(ts.Variables.SSLDir, SERVER_NAME_IN_CERT), +}) + +request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993"} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993"} +server.addResponse("sessionfile.log", request_header, response_header) + +ts.addSSLfile("ssl/ca.pem") +ts.addSSLfile("ssl/ca.key") +ts.addSSLfile("ssl/{0}.pem".format(SERVER_NAME_IN_CERT)) +ts.addSSLfile("ssl/{0}.key".format(SERVER_NAME_IN_CERT)) + +dns = Test.MakeDNServer("dns") +dns.addRecords(records = { + SERVER_NAME_IN_CERT: ['127.0.0.1'], + SYNTH_SERVER_NAME: ['127.0.0.1'], +}) + +ts.Disk.records_config.update({ + # reduce the number of unnecessary retries in negative case + 'proxy.config.http.connect_attempts_max_retries': 0, + # CA cert is required to pass the positive case + 'proxy.config.ssl.client.CA.cert.path': '{}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.CA.cert.filename': 'ca.pem', + 'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port), + 'proxy.config.dns.resolv_conf': 'NULL', +}) + +ts.Disk.remap_config.AddLine( + 'map /positive https://{0}:{1} @plugin=ssl_verify_remap.so @pparam={0}'.format(SERVER_NAME_IN_CERT, server.Variables.Port) +) +ts.Disk.remap_config.AddLine( + 'map /negative https://{0}:{1} @plugin=ssl_verify_remap.so @pparam={0}'.format(SYNTH_SERVER_NAME, server.Variables.Port) +) + +ts.Disk.diags_log.Content = Testers.ContainsExpression('ERROR:', 'SSL3_GET_SERVER_CERTIFICATE:certificate verify failed') + +Test.PreparePlugin(os.path.join(Test.Variables.AtsTestToolsDir, 'plugins', 'ssl_verify_remap.cc'), ts) + +tr = Test.AddTestRun('Positive') +tr.Processes.Default.StartBefore(Test.Processes.ts) +tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port)) +tr.Processes.Default.StartBefore(dns) +tr.Processes.Default.Command = 'curl -sf -o /dev/null http://127.0.0.1:{0}/positive'.format(ts.Variables.port) +tr.Processes.Default.ReturnCode = 0 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +tr.StillRunningAfter = dns + +tr = Test.AddTestRun('Negative') +tr.Processes.Default.Command = 'curl -sf -o /dev/null http://127.0.0.1:{0}/negative'.format(ts.Variables.port) +tr.Processes.Default.ReturnCode = 22 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +tr.StillRunningAfter = dns diff --git a/tests/tools/plugins/ssl_verify_remap.cc b/tests/tools/plugins/ssl_verify_remap.cc new file mode 100644 index 00000000000..9d03055c00b --- /dev/null +++ b/tests/tools/plugins/ssl_verify_remap.cc @@ -0,0 +1,141 @@ +/** @file + + A plugin verifies DNS name in X509 certificate. + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#include "ts/ts.h" +#include "ts/remap.h" +#include +#include + +#define PLUGIN_NAME "ssl_verify_remap" + +int +ssl_verify_callback(void *arg, int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + if (preverify_ok == 0) { + return 0; + } + + const int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + if (depth != 0) { + return preverify_ok; + } + + X509 *cert = X509_STORE_CTX_get_current_cert(x509_ctx); + GENERAL_NAMES *subject_alt_names = static_cast(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)); + if (subject_alt_names == nullptr) { + return 0; + } + + const char *dns_to_match = static_cast(arg); + const int alt_name_count = sk_GENERAL_NAME_num(subject_alt_names); + bool found = false; + for (int i = 0; i < alt_name_count; ++i) { + const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(subject_alt_names, i); + if (current_name->type == GEN_DNS) { + ASN1_IA5STRING *name_dns = current_name->d.ia5; + const char *dns_str = reinterpret_cast(ASN1_STRING_get0_data(name_dns)); + const size_t dns_len = static_cast(ASN1_STRING_length(name_dns)); + if (strncmp(dns_to_match, dns_str, dns_len) == 0) { + found = true; + break; + } + } + } + if (found) { + return 1; + } else { + return 0; + } +} + +int +create_verify_callback(TSCont contp, TSEvent event, void *edata) +{ + const TSHttpTxn txnp = static_cast(edata); + + TSVConn vc = TSHttpTxnServerVConnGet(txnp); + if (vc == nullptr) { + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); + return TS_EVENT_NONE; + } + + if (!TSVConnVerifyCallbackSet(vc, reinterpret_cast(&ssl_verify_callback), TSContDataGet(contp))) { + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_ERROR); + return TS_EVENT_NONE; + } + + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return TS_EVENT_NONE; +} + +void +TSPluginInit(int argc, const char *argv[]) +{ + TSPluginRegistrationInfo info; + + info.plugin_name = PLUGIN_NAME; + info.vendor_name = "Apache Software Foundation"; + info.support_email = "dev@trafficserver.apache.org"; + + if (TSPluginRegister(&info) != TS_SUCCESS) { + TSError("[%s] Plugin registration failed", PLUGIN_NAME); + TSError("[%s] Unable to initialize plugin (disabled)", PLUGIN_NAME); + } + + return; +} + +TSReturnCode +TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) +{ + return TS_SUCCESS; +} + +TSReturnCode +TSRemapNewInstance(int argc, char *argv[], void **ih, char * /* errbuf ATS_UNUSED */, int /* errbuf_sizeATS_UNUSED */) +{ + if (argc < 3) { + return TS_ERROR; + } else { + *ih = TSstrdup(argv[2]); + return TS_SUCCESS; + } +} + +void +TSRemapDeleteInstance(void *ih) +{ + TSfree(ih); +} + +TSRemapStatus +TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo *rri) +{ + if (ih != nullptr) { + const TSCont create_verify_callback_contp = TSContCreate(create_verify_callback, nullptr); + TSContDataSet(create_verify_callback_contp, ih); + TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, create_verify_callback_contp); + } + + return TSREMAP_NO_REMAP; +}