/* httpda.c gSOAP HTTP Digest Authentication plugin. Adds support for digest authentication RFC2617 and the RFC7616 draft. gSOAP XML Web services tools Copyright (C) 2000-2016, Robert van Engelen, Genivia Inc., All Rights Reserved. This part of the software is released under one of the following licenses: GPL or the gSOAP public license. -------------------------------------------------------------------------------- gSOAP public license. The contents of this file are subject to the gSOAP Public License Version 1.3 (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.cs.fsu.edu/~engelen/soaplicense.html Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. The Initial Developer of the Original Code is Robert A. van Engelen. Copyright (C) 2000-2016, Robert van Engelen, Genivia, Inc., All Rights Reserved. -------------------------------------------------------------------------------- GPL license. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Author contact information: engelen@genivia.com / engelen@acm.org This program is released under the GPL with the additional exemption that compiling, linking, and/or using OpenSSL is allowed. -------------------------------------------------------------------------------- A commercial use license is available from Genivia, Inc., contact@genivia.com -------------------------------------------------------------------------------- */ /** @mainpage - @ref httpda documents the http_da plugin for HTTP Digest Authentication. */ /** @page httpda The HTTP-DA plugin [TOC] @section httpda_0 Introduction The upgraded HTTP digest authentication plugin for gSOAP adds support for the RFC7616 draft that is backwards compatible with RFC2617. The new plugin adds SHA-256 (and SHA-512/256 when OpenSSL supports it) algorithms, including the -sess variants. To maintain backwards compatibility with RFC2617 the MD5 algorithm is still supported but not recommended. HTTP **digest authentication** does not transmit the user id and password for authentication. Instead, a server negotiates credentials (username and/or password) with a client using cryptographic hashing algorithms with nonce values to prevent replay attacks. By contrast, HTTP **basic authentication** is not safe over unencrypted channels because the password is transmitted to the server unencrypted. Therefore, this mechanism provides no confidentiality protection for the transmitted credentials. HTTPS is typically preferred over or used in conjunction with HTTP basic authentication. To support HTTP digest authentication in favor of HTTP basic authentication, you will need to install OpenSSL and follow these steps to build your projects: - Compile your project that uses gSOAP source code with `-DWITH_OPENSSL`. - Link libgsoapssl (libgsoapssl++), or use the `stdsoap2.c[pp]` source. - Compile and link your code together with `plugin/httpda.c`, `plugin/smdevp.c`, and `plugin/threads.c` The plugin is MT-safe by means of internal mutex locks. Mutex ensures exclusive access and updates of the shared session store with nonces to prevent replay attacks. @section httpda_1 Client-side usage HTTP basic authentication is the default authentication mechanism supported by gSOAP. You can set the basic authentication credentials at the client-side with: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} soap.userid = ""; soap.passed = ""; if (soap_call_ns__method(&soap, ...)) ... // error ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ or if you use a proxy object generated with saopcpp2 option -j: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} Proxy proxy(...); proxy.soap->userid = ""; proxy.soap->passed = ""; if (proxy.method(...)) ... // error ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTTP basic authentication should **never** be used over plain HTTP, because the credentials (the ID and password) are transmitted in the clear in base64 encoded form which is easily reversible. This mechanism is safer to use over HTTPS, because the HTTP headers and body are encrypted. This upgraded HTTP digest authentication plugin supports RFC7616 and RFC2617. RFC7616 adds SHA2 and is backwards compatible to clients that use MD5. The MD5 algorithm is not allowed in FIPS making SHA-256 or SHA-512-256 digest algorithms mandatory. The client-side of the plugin handles both RFCs automatically. To use HTTP digest authentication with gSOAP, register the http_da plugin as follows: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include "httpda.h" struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); ... soap_destroy(soap); // deallocate data soap_end(soap); // deallocate temp data soap_free(soap); // deregister plugin and deallocate context ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ or if you use a proxy object generated with saopcpp2 option -j: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} #include "httpda.h" Proxy proxy(...); soap_register_plugin(proxy.soap, http_da); ... proxy.destroy(); // deallocate data and temps ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To make a client-side service call you will need to create a digest store `http_da_info`. The store holds the digest information locally on your machine to manage repeated authentication challenges from all servers you connect to. Use `http_da_save()` to add credentials to the store and release the store with `http_da_release()` when you no longer need the credentials. The `http_da_info` store is intended for one thread to issue a sequence of calls that are all authenticated without requiring (re)negotiation. You should not share the `http_da_info` store with multiple threads, unless you use mutex locks. Here is an example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); struct http_da_info info; if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 401) // HTTP authentication is required { http_da_save(soap, &info, "", "", ""); if (soap_call_ns__method(soap, ...)) // make a call with authentication ... // error http_da_release(soap, &info); // release if auth is no longer needed } else ... // other error } soap_destroy(soap); soap_end(soap); soap_free(soap); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Again, if you use a proxy object then replace the `soap_call_ns__method` with the proxy method invocation, as was shown earlier. The `` string is the protected realm of the server that requires authorization. This string can be obtained with the `soap.authrealm` string after an unsuccessful non-authenticated call so you can use it to save credentials to the digest store: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); struct http_da_info info; if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 401) // HTTP authentication is required { const char *realm = soap.authrealm; http_da_save(soap, &info, realm, "", ""); if (soap_call_ns__method(soap, ...)) // make a call with authentication ... // error ... http_da_release(soap, &info); // deallocate authentication info if auth is no longer needed } else ... // error } soap_destroy(soap); soap_end(soap); soap_free(soap); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Before a second call is made to the same endpoint that requires authentication, you must restore the authentication state with `http_da_restore()`, then use it, and finally release it with `http_da_release()`: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); struct http_da_info info; bool auth = false; if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 401) // HTTP authentication is required { http_da_save(soap, &info, "", "", ""); auth = true; } else ... // other error } if (auth) http_da_restore(soap, &info); if (soap_call_ns__method(soap, ...)) // make a call with authentication ... // error soap_destroy(soap); // okay to dealloc data soap_end(soap); // okay to dealloc data if (auth) http_da_restore(soap, &info); if (soap_call_ns__method(soap, ...)) // make a call with authentication ... // error if (auth) http_da_release(soap, &info); // deallocate authentication info soap_destroy(soap); soap_end(soap); soap_free(soap); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For HTTP proxies requiring HTTP digest authenticaiton, use the 'proxy' functions of the plugin: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); struct http_da_info info; if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 407) // HTTP proxy authentication is required { http_da_proxy_save(soap, &info, "", "", ""); auth = true; } else ... // error } if (auth) http_da_proxy_restore(soap, &info); if (soap_call_ns__method(soap, ...)) ... // error http_da_proxy_release(soap, &info); // deallocate authentication info soap_destroy(soap); soap_end(soap); soap_free(soap); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @section httpda_2 Client example A client authenticating against a server: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 401) // HTTP authentication is required { if (!strcmp(soap.authrealm, authrealm)) // check authentication realm { struct http_da_info info; // to store userid and passwd http_da_save(soap, &info, authrealm, userid, passwd); // call again, now with credentials if (soap_call_ns__method(soap, ...) == SOAP_OK) { ... // process response data soap_end(soap); ... // userid and passwd were deallocated (!) http_da_restore(soap, &info); // get userid and passwd after soap_end() if (!soap_call_ns__method(soap, ...) == SOAP_OK) ... // error http_da_release(soap, &info); // deallocate authentication info ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A client authenticating against a proxy: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); if (soap_call_ns__method(soap, ...)) // make a call without authentication { if (soap.error == 407) // HTTP authentication is required { if (!strcmp(soap.authrealm, authrealm)) // check authentication realm { struct http_da_info info; // to store userid and passwd http_da_proxy_save(soap, &info, authrealm, userid, passwd); // call again, now with credentials if (soap_call_ns__method(soap, ...) == SOAP_OK) { ... // process response data soap_end(soap); ... // userid and passwd were deallocated (!) http_da_proxy_restore(soap, &info); // get userid and passwd after soap_end() if (!soap_call_ns__method(soap, ...) == SOAP_OK) ... // error http_da_proxy_release(soap, &info); // deallocate authentication info ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @section httpda_3 Server-side usage As explained in the gSOAP user guid, server-side HTTP basic authentication is enforced by simply checking the `soap.userid` and `soap.passwd` values in a service method that requires client authentication: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); ... soap_serve(soap); // see gSOAP documentation and examples on how to serve requests ... int ns__method(struct soap *soap, ...) { if (!soap->userid || !soap->passwd || strcmp(soap->userid, "") || strcmp(soap->passwd, "")) return 401; // HTTP authentication required ... } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTTP digest authentication is verified differently, because digests are compared, not passwords: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} struct soap *soap = soap_new(); soap_register_plugin(soap, http_da); ... soap_serve(soap); // see gSOAP documentation and examples on how to serve requests ... int ns__method(struct soap *soap, ...) { if (soap->authrealm && soap->userid) { passwd = ... // database lookup on userid and authrealm to find passwd if (!strcmp(soap->authrealm, authrealm) && !strcmp(soap->userid, userid)) { if (!http_da_verify_post(soap, passwd)) // HTTP POST DA verification { ... // process request and produce response return SOAP_OK; } } } soap->authrealm = authrealm; // realm to send to client return 401; // Not authorized, challenge with digest authentication ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The `http_da_verify_post()` function checks the HTTP POST credentials by computing and comparing a digest of the password. The HTTP POST method is used for two-way SOAP/XML communications with request and response messages. One-way SOAP/XML messaging may use HTTP POST or HTTP GET. To verify an HTTP GET operation, use `http_da_verify_get()` instead, for example in the HTTP GET handler implemented with the HTTP GET plugin. Likewise, use `http_da_verify_put()` for HTTP PUT, `http_da_verify_patch()` for HTTP PATCH, and `http_da_verify_del()` for HTTP DELETE. To verify other HTTP methods, use the `http_da_verify_method()` function with the method as a string argument, for example: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} if (!http_da_verify_method(soap, "HEAD", passwd)) { ... // process the request } ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Server-side operations that handle other methods than HTTP POST and GET, such as PATCH, PUT, and DELETE should be implemented with the HTTP POST plugin. This plugin uses a dispatch table with a handler corresponding to the HTTP method to serve. RFC7616 recommends SHA2 over MD5. The MD5 algorithm is not allowed in FIPS and SHA-256 or SHA-512-256 are mandatory. This upgraded HTTP digest plugin uses SHA-256 as the default algorithm and reverts to MD5 only if required by a client that does not support RFC7616. The default SHA-256 digest algorithm is enabled automatically. However, at the server side you can also use a plugin registry option to set a different algorithm as the default: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} soap_register_plugin_arg(soap, http_da,