From 56993b9e2d701af41ad359a7e475d0ebec3c079e Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Wed, 1 Sep 2021 01:43:35 +0300 Subject: [PATCH] net.http: support passing client certificates in http.fetch (#11356) --- examples/fetch.v | 2 +- vlib/net/http/backend_nix.c.v | 52 ++++++++++++++++++++++++++++------- vlib/net/http/http.v | 9 ++++++ vlib/net/http/request.v | 5 ++++ vlib/net/openssl/c.v | 8 +++++- 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/examples/fetch.v b/examples/fetch.v index e1a7132acc..9b804da92e 100644 --- a/examples/fetch.v +++ b/examples/fetch.v @@ -3,7 +3,7 @@ import net.http fn main() { resp := http.get('https://vlang.io/utc_now') or { - println('failed to fetch data from the server') + eprintln('Failed to fetch data from the server. Error: $err') return } diff --git a/vlib/net/http/backend_nix.c.v b/vlib/net/http/backend_nix.c.v index 1243442a0d..06112b408f 100644 --- a/vlib/net/http/backend_nix.c.v +++ b/vlib/net/http/backend_nix.c.v @@ -13,11 +13,41 @@ const ( fn (req &Request) ssl_do(port int, method Method, host_name string, path string) ?Response { // ssl_method := C.SSLv23_method() ctx := C.SSL_CTX_new(C.TLS_method()) + defer { + if ctx != 0 { + C.SSL_CTX_free(ctx) + } + } C.SSL_CTX_set_verify_depth(ctx, 4) flags := C.SSL_OP_NO_SSLv2 | C.SSL_OP_NO_SSLv3 | C.SSL_OP_NO_COMPRESSION C.SSL_CTX_set_options(ctx, flags) - mut res := C.SSL_CTX_load_verify_locations(ctx, c'random-org-chain.pem', 0) + // Support client certificates: + mut res := 0 + if req.verify != '' { + res = C.SSL_CTX_load_verify_locations(ctx, &char(req.verify.str), 0) + if req.validate && res != 1 { + return error('http: openssl: SSL_CTX_load_verify_locations failed') + } + } + if req.cert != '' { + res = C.SSL_CTX_use_certificate_file(ctx, &char(req.cert.str), C.SSL_FILETYPE_PEM) + if req.validate && res != 1 { + return error('http: openssl: SSL_CTX_use_certificate_file failed, res: $res') + } + } + if req.cert_key != '' { + res = C.SSL_CTX_use_PrivateKey_file(ctx, &char(req.cert_key.str), C.SSL_FILETYPE_PEM) + if req.validate && res != 1 { + return error('http: openssl: SSL_CTX_use_PrivateKey_file failed, res: $res') + } + } + // the setup is done, prepare an ssl connection from the SSL context: web := C.BIO_new_ssl_connect(ctx) + defer { + if web != 0 { + C.BIO_free_all(web) + } + } addr := host_name + ':' + port.str() res = C.BIO_set_conn_hostname(web, addr.str) ssl := &openssl.SSL(0) @@ -25,16 +55,24 @@ fn (req &Request) ssl_do(port int, method Method, host_name string, path string) preferred_ciphers := 'HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4' res = C.SSL_set_cipher_list(voidptr(ssl), &char(preferred_ciphers.str)) if res != 1 { - println('http: openssl: cipher failed') + return error('http: openssl: SSL_set_cipher_list failed, res: $res') } res = C.SSL_set_tlsext_host_name(voidptr(ssl), host_name.str) res = C.BIO_do_connect(web) if res != 1 { - return error('cannot connect the endpoint') + return error('http: openssl: BIO_do_connect failed, res: $res') } res = C.BIO_do_handshake(web) - C.SSL_get_peer_certificate(voidptr(ssl)) + pcert := C.SSL_get_peer_certificate(voidptr(ssl)) + defer { + if pcert != 0 { + C.X509_free(pcert) + } + } res = C.SSL_get_verify_result(voidptr(ssl)) + if req.validate && res != C.X509_V_OK { + return error('http: openssl: SSL_get_verify_result failed, res: $res') + } // ///// req_headers := req.build_request_headers(method, host_name, path) $if trace_http_request ? { @@ -60,12 +98,6 @@ fn (req &Request) ssl_do(port int, method Method, host_name string, path string) } unsafe { content.write_ptr(bp, len) } } - if web != 0 { - C.BIO_free_all(web) - } - if ctx != 0 { - C.SSL_CTX_free(ctx) - } response_text := content.str() $if trace_http_response ? { eprintln('< $response_text') diff --git a/vlib/net/http/http.v b/vlib/net/http/http.v index 7bdc5e2bd7..3b4852f6e6 100644 --- a/vlib/net/http/http.v +++ b/vlib/net/http/http.v @@ -22,6 +22,11 @@ pub mut: cookies map[string]string user_agent string = 'v.http' verbose bool + // + validate bool // set this to true, if you want to stop requests, when their certificates are found to be invalid + verify string // the path to a rootca.pem file, containing trusted CA certificate(s) + cert string // the path to a cert.pem file, containing client certificate(s) for the request + cert_key string // the path to a key.pem file, containing private keys for the client certificate(s) } pub fn new_request(method Method, url_ string, data string) ?Request { @@ -119,6 +124,10 @@ pub fn fetch(config FetchConfig) ?Response { user_agent: config.user_agent user_ptr: 0 verbose: config.verbose + validate: config.validate + verify: config.verify + cert: config.cert + cert_key: config.cert_key } res := req.do() ? return res diff --git a/vlib/net/http/request.v b/vlib/net/http/request.v index 4664659ade..d6c53c38f0 100644 --- a/vlib/net/http/request.v +++ b/vlib/net/http/request.v @@ -26,6 +26,11 @@ pub mut: // time = -1 for no timeout read_timeout i64 = 30 * time.second write_timeout i64 = 30 * time.second + // + validate bool // when true, certificate failures will stop further processing + verify string + cert string + cert_key string } fn (mut req Request) free() { diff --git a/vlib/net/openssl/c.v b/vlib/net/openssl/c.v index dedba2a94a..e744c3a616 100644 --- a/vlib/net/openssl/c.v +++ b/vlib/net/openssl/c.v @@ -63,10 +63,14 @@ fn C.SSL_CTX_set_options(ctx &C.SSL_CTX, options int) fn C.SSL_CTX_set_verify_depth(s &C.SSL_CTX, depth int) -fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, ca_file &char, ca_path &char) int +fn C.SSL_CTX_load_verify_locations(ctx &C.SSL_CTX, const_file &char, ca_path &char) int fn C.SSL_CTX_free(ctx &C.SSL_CTX) +fn C.SSL_CTX_use_certificate_file(ctx &C.SSL_CTX, const_file &char, file_type int) int + +fn C.SSL_CTX_use_PrivateKey_file(ctx &C.SSL_CTX, const_file &char, file_type int) int + fn C.SSL_new(&C.SSL_CTX) &C.SSL fn C.SSL_set_fd(ssl &C.SSL, fd int) int @@ -77,6 +81,8 @@ fn C.SSL_set_cipher_list(ctx &SSL, str &char) int fn C.SSL_get_peer_certificate(ssl &SSL) &C.X509 +fn C.X509_free(const_cert &C.X509) + fn C.ERR_clear_error() fn C.SSL_get_error(ssl &C.SSL, ret int) int