Genivia - WS-Security lite

|  | Home | Documentation | WS-Security lite updated Fri Jul 17 2020 by Robert van Engelen |

- Main Page
- Related Pages
- Classes
- Files

WS-Security lite

# Security Header

The material in this section relates to the WS-Security specification section 5.

To use the wsse lite API:

1. Run wsdl2h -t typemap.dat on a WSDL of a service that requires WS-Security headers. The typemap.dat file is used to recognize and translate Security header blocks.
2. Run soapcpp2 on the header file produced by wsdl2h.
3. Use the wsse lite API functions described below to add time stamp and user name tokens.

If HTTPS is required with OpenSSL then please follow the instructions in Section WS-Security and HTTPS to ensure thread-safety of WS-Security with HTTPS.

The wsse lite API is located in:

- `gsoap/plugin/wsseapi-lite.h` wsse lite API.
- `gsoap/plugin/wsseapi-lite.c` wsse lite API for C and C++.

You will also need:

- `gsoap/custom/struct_timeval.c` compile and link this file (C and C++).
- compile all sources with `-DWITH_OPENSSL` to enable HTTPS.
- if you have zlib installed, compile all sources also with `-DWITH_GZIP`.
- link with `-lssl -lcrypto -lz -gsoapssl++` (or `-lgsoapssl` for C, or compile `stdsoap2.cpp` for C++ and `stdsoap2.c` for C).

The gSOAP header file for soapcpp2 should import wsse.h (or the older 2002 version wsse2.h):

#import "wsse.h"

The wsdl2h tool adds the necessary imports to the generated header file if the WSDL declares the use of WS-Security. If not, you may have to add the import manually before running soapcpp2.

The wsse lite API consists of a set of functions to populate and verify WS-Security headers and message body content. For more details, we refer to the following sections that correspond to the WS-Security specification sections:

- Section 6 Security Tokens
- Section 10 Security Timestamps
- WS-Security and HTTPS

The basic API is introduced below.

To add an empty Security header block to the SOAP header, use:

soap_wsse_add_Security(soap);

To delete a Security header, use:

soap_wsse_delete_Security(soap);

Adding an empty Security header block is not very useful. In the following, we present the higher-level functions of the wsse lite API to populate and verify Security header content.

Note
  The soap context includes an actor value soap.actor that is populated and rendered as the SOAP-ENV:actor (SOAP 1.1) or SOAP-ENV:role (SOAP 1.2) attribute in XML within the generic SOAP Header. The attribute is optional, but should be used to target a recipient such as an intermediate node to process the SOAP header. In contrast, actor or role attributes within Security header blocks target specific recipients to process the Security header block. The gSOAP implementation does not automate this feature and application should set and check the actor/role attribute when necessary. In addition, the current implementation supports the inclusion of a single Security header block in the SOAP header.

To populate the SOAP-ENV:actor or SOAP-ENV:role attribute within the Security header, use:

soap_wsse_add_Security_actor(soap, "recipient");

To obtain the actor or role value (e.g. after receiving a message), use:

_wsse__Security *security = soap_wsse_Security(soap);
if (security)
{
... = security->SOAP_ENV__actor; // SOAP 1.1
... = security->SOAP_ENV__role; // SOAP 1.2

The SOAP-ENV:mustUnderstand attribute is automatically added and checked by the gSOAP engine. A gSOAP application compiled without Security support will reject Security headers.

Security header blocks are attached to the soap context, which means that the information will be automatically kept to support multiple invocations.

# Security Tokens

The material in this section relates to the WS-Security specification section 6.

## User Name Token

To add a user name token to the Security header block, use:

soap_wsse_add_UsernameTokenText(soap, "Id", "username", NULL);

The `Id` value is optional and not used in the wsse lite API. These `Id`s are serialized as wsu:Id identifiers for cross-referencing XML elements.

To add a user name token with clear text password, use:

soap_wsse_add_UsernameTokenText(soap, "Id", "username", "password");

It is strongly recommended to use `soap_wsse_add_UsernameTokenText` only in combination with HTTPS encrypted transmission or not at all. A better alternative is to use password digests (not supported in this wsse lite API).

Clear-text passwords are verified with `soap_wsse_verify_Password`. To verify a password at the receiving side to authorize a request (e.g. within a Web service operation), use:

int ns__myMethod(struct soap *soap, ...)
{
const char *username = soap_wsse_get_Username(soap);
const char *password;
if (!username)
return soap->error; // no username: return FailedAuthentication (from soap_wsse_get_Username)
password = ...; // lookup password of username
if (soap_wsse_verify_Password(soap, password))
{
int err = soap->error;
soap_wsse_delete_Security(soap); // remove old security headers
return err; // password verification failed: return FailedAuthentication
}
return SOAP_OK;
}

Note that the `soap_wsse_get_Username` functions sets the wsse:FailedAuthentication fault upon failure. It is common for the API functions functions to return SOAP_OK or a wsse fault that should be passed to the sender by returning soap->error from service operations. The fault is displayed with the `soap_print_fault` function.

# Security Timestamps

The material in this section relates to the WS-Security specification section 10.

To add a timestamp with the creation time to the Security header, use:

soap_wsse_add_Timestamp(soap, NULL, 0); // no expiration

The lifetime of a message (in seconds) is passed as the third argument, which will be displayed as the timestamp expiration time:

soap_wsse_add_Timestamp(soap, NULL, 10); // 10 seconds lifetime

# WS-Security and HTTPS

HTTPS is used at the client side with the usual "https:" URL addressing, shown here:

#include "wsseapi-lite.h"
#include "threads.h"
struct soap *soap;
if (CRYPTO_thread_setup())
... // error
soap = soap_new1(SOAP_XML_CANONICAL | SOAP_XML_INDENT);
if (soap_ssl_client_context(&soap,
SOAP_SSL_DEFAULT, // requires server authentication
NULL, // keyfile for client authentication to server
NULL, // the keyfile password
"cacerts.pem", // cafile CA certificates to authenticate the server
NULL, // capath CA directory path to certificates
NULL
))
... // error
soap->cafile = "cacerts.pem"; // same as above (or overrides the above)
soap->capath = "dir/to/certs"; // and/or point to CA certs
soap->crlfile = "revoked.pem"; // use CRL (optional)
soap_wsse_delete_Security(soap); // remove any previous header content
soap_wsse_add_UsernameTokenText(soap, NULL, "username", "password");
soap_wsse_add_Timestamp(soap, NULL, 10); // 10 seconds lifetime
if (soap_call_ns__myMethod(soap, "https://...", ...))
... // error
... // process response results
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
CRYPTO_thread_cleanup();

With OpenSSL, the CRYPTO threads should be set up before any threads are created.

The `soap_ssl_client_context` only needs to be set up once. Use the following flags:

- `SOAP_SSL_DEFAULT` requires server authentication, CA certs should be used
- `SOAP_SSL_NO_AUTHENTICATION` disables server authentication
- `SOAP_SSL_SKIP_HOST_CHECK` disables server authentication host check
- `SOAP_SSL_ALLOW_EXPIRED_CERTIFICATE` to accept self-signed certificates, expired certificates, and certificates without CRL.

The server uses the following:

#include "wsseapi-lite.h"
#include "threads.h"
SOAP_SOCKET m, s;
int port = 443;
struct soap *soap;
if (CRYPTO_thread_setup())
... // error
soap = soap_new1(SOAP_XML_CANONICAL | SOAP_XML_INDENT);
if (soap_ssl_server_context(&soap,
SOAP_SSL_DEFAULT, // requires server to authenticate, but not the client
server.pem, // keyfile for authentication to client
"password", // the keyfile password
NULL, // CA certificates to authenticate the client
NULL, // CA directory path to certificates
NULL, // use RSA 2048 bits (or give file name with DH param)
NULL,
NULL
))
... // error
if (!soap_valid_socket(m = soap_bind(soap, NULL, port, 100))
... // error
for (;;)
{
if (!soap_valid_socket(s = soap_accept(soap)))
... // error
else
{
struct soap *tsoap = soap_copy(soap);
while (THREAD_CREATE(&tid, (void*(*)(void*))&process_request, (void*)tsoap))
sleep(1);
}
}
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
CRYPTO_thread_cleanup();

where we define a process_request function that is executed by the thread to process the request (on a copy of the soap context struct):

void *process_request(struct soap *soap)
{
if (soap_ssl_accept(soap)
|| soap_serve(soap))
... // error
soap_destroy(soap);
soap_end(soap);
soap_free(soap);
}

The `soap_ssl_server_context` only needs to be set up once. Use the following flags:

- `SOAP_SSL_DEFAULT` requires server authentication, but no client authentication
- `SOAP_SSL_REQUIRE_CLIENT_AUTHENTICATION` requires client authentication

With OpenSSL, we need to define the thread set up and clean up operations as follows:

struct CRYPTO_dynlock_value
{
MUTEX_TYPE mutex;
};
static MUTEX_TYPE *mutex_buf;
static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
{
struct CRYPTO_dynlock_value *value;
value = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value));
if (value)
MUTEX_SETUP(value->mutex);
return value;
}
static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(l->mutex);
else
MUTEX_UNLOCK(l->mutex);
}
static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
MUTEX_CLEANUP(l->mutex);
free(l);
}
void locking_function(int mode, int n, const char *file, int line)
{
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(mutex_buf[n]);
else
MUTEX_UNLOCK(mutex_buf[n]);
}
unsigned long id_function()
{
return (unsigned long)THREAD_ID;
}
int CRYPTO_thread_setup()
{
int i;
mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
if (!mutex_buf)
return SOAP_EOM;
for (i = 0; i < CRYPTO_num_locks(); i++)
MUTEX_SETUP(mutex_buf[i]);
CRYPTO_set_id_callback(id_function);
CRYPTO_set_locking_callback(locking_function);
CRYPTO_set_dynlock_create_callback(dyn_create_function);
CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
return SOAP_OK;
}
void CRYPTO_thread_cleanup()
{
int i;
if (!mutex_buf)
return;
CRYPTO_set_id_callback(NULL);
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_dynlock_create_callback(NULL);
CRYPTO_set_dynlock_lock_callback(NULL);
CRYPTO_set_dynlock_destroy_callback(NULL);
for (i = 0; i < CRYPTO_num_locks(); i++)
MUTEX_CLEANUP(mutex_buf[i]);
free(mutex_buf);
mutex_buf = NULL;
}

For additional details and examples, see the user guide and examples in the gSOAP package directory `gsoap/samples/ssl`.