diff --git a/TLS.md b/TLS.md
index 0561ae9..742d11e 100644
--- a/TLS.md
+++ b/TLS.md
@@ -60,7 +60,7 @@ curl -v --cacert server.pub --url https://localhost:35800
```
-## Certificate Information
+## Server Certificate Information
Information about the used server certificate can be queried during runtime. The data is available
via the thread local storage which is a noxDB graph and can be queried with the noxDB API, see
@@ -81,10 +81,74 @@ certificate values can be accessed via their corresponding keys. The keys are ba
GSKit certificate data ids. The value for `CERT_COMMON_NAME` can be access by the key `common_name`.
All values are strings.
-The certificate is only available if it has been enabled by calling `il_setTlsServerCertEnabled`.
+The certificate information is only available if it has been enabled by calling `il_setTlsServerCertEnabled`,
+`Tls` in this case means thread local storage.
```
il_setTlsServerCertEnabled(config : IL_TRUE);
```
-For more information and available keys see the GSKit API [gsk_attribute_get_cert_info](https://www.ibm.com/docs/en/i/7.4.0?topic=ssw_ibm_i_74/apis/gsk_attribute_get_cert_info.html).
\ No newline at end of file
+For more information and available keys see the GSKit API [gsk_attribute_get_cert_info](https://www.ibm.com/docs/en/i/7.4.0?topic=ssw_ibm_i_74/apis/gsk_attribute_get_cert_info.html).
+
+
+
+## mTLS
+
+ILEastic supports mutual TLS which means that not only the server certificate is checked by the
+client but the client must also send a certficiate which is validated by the server).
+
+This can be enabled by setting the `client auth mode` to something other than NONE.
+
+With the configuration set to IL_CLIENT_AUTH_MODE_REQUIRED the client must provide a valid client
+certificate to establish a TLS connection.
+
+With the configuration set to IL_CLIENT_AUTH_MODE_PASSTHRU the client is asked for a client
+certficate but does not need to provide one.
+
+A requirement for this is having TLS enabled by providing a server certificate in the
+configured keystore file.
+
+All client certificates must also be available in the same keystore file.
+
+### Client Certificate Information
+
+Information about the provided client certificate can be queried during runtime. The data is available
+via the thread local storage which is a noxDB graph and can be queried with the noxDB API, see
+`il_getThreadMem`.
+
+Example:
+
+```
+dcl-s tls pointer;
+dcl-s value varchar(100);
+
+tls = il_getThreadMem(request);
+value = jx_getStr(tls : '/ileastic/certificate/client/common_name');
+```
+
+The client certificate information is stored at the path `/ileastic/certificate/client`. The
+certificate values can be accessed via their corresponding keys. The keys are based on the
+GSKit certificate data ids. The value for `CERT_COMMON_NAME` can be access by the key `common_name`.
+All values are strings.
+
+The certificate information is only available if it has been enabled by calling `il_setTlsClientCertEnabled`,
+`Tls` in this case means thread local storage.
+
+```
+il_setTlsClientCertEnabled(config : IL_TRUE);
+```
+
+For more information and available keys see the GSKit API [gsk_attribute_get_cert_info](https://www.ibm.com/docs/en/i/7.4.0?topic=ssw_ibm_i_74/apis/gsk_attribute_get_cert_info.html).
+
+### Client Certifcate Validation Result
+
+The result of the validation of the client certificate can be queried via the thread local storage
+at `/ileastic/certificate/client/validationcode`. The value is the returned value of the call to
+`gsk_attribute_get_numeric_value` with enum id `GSK_CERTIFICATE_VALIDATION_CODE`.
+
+TLDR: validationcode = 0 means client certificate is valid
+
+### Plugins
+
+Plugins can access the certificate information by querying the thread local store. This can be used
+to further filter requests by custom criterias.
diff --git a/headers/ileastic.bnd b/headers/ileastic.bnd
index 6202334..9c6d369 100644
--- a/headers/ileastic.bnd
+++ b/headers/ileastic.bnd
@@ -36,4 +36,6 @@ STRPGMEXP PGMLVL(*CURRENT) SIGNATURE('ILEastic 1.1.2')
EXPORT SYMBOL("il_mediatype_parseMediaType")
EXPORT SYMBOL("il_setKeyfile")
EXPORT SYMBOL("il_setTlsServerCertEnabled")
+ EXPORT SYMBOL("il_setTlsClientCertEnabled")
+ EXPORT SYMBOL("il_setClientAuthMode")
ENDPGMEXP
diff --git a/headers/ileastic.rpgle b/headers/ileastic.rpgle
index 96dda86..bd56e34 100644
--- a/headers/ileastic.rpgle
+++ b/headers/ileastic.rpgle
@@ -366,9 +366,28 @@ dcl-c IL_MEDIA_TYPE_JSON 'application/json';
///
dcl-c IL_MEDIA_TYPE_XML 'application/xml';
+///
+// Generic true constant
+///
dcl-c IL_TRUE 1;
+///
+// Generic false constant
+///
dcl-c IL_FALSE 0;
+///
+// mTLS : No client certificate requested (no mTLS)
+///
+dcl-c IL_CLIENT_AUTH_MODE_NONE 0;
+///
+// mTLS : client certificate is required else TLS session will not be started
+///
+dcl-c IL_CLIENT_AUTH_MODE_REQUIRED 1;
+///
+// mTLS : client certificate is always request but does not need to be provided by the client. TLS session is always started.
+///
+dcl-c IL_CLIENT_AUTH_MODE_PASSTHRU 2;
+
///
// Configuration
///
@@ -382,6 +401,8 @@ dcl-ds il_config qualified template;
isWorker ind;
threadingMode int(5);
tlsServerCertEnabled int(3);
+ tlsClientCertEnabled int(3);
+ clientAuthMode int(3);
filler char(4096); // required - contains the private internal handlers
end-ds;
@@ -1067,7 +1088,7 @@ dcl-pr il_setKeyfile extproc(*dclcase);
end-pr;
///
-// Enable Server Certificate Infos in TLS
+// Enable server certificate infos in TLS
//
// If enabled the available infos about the used server certificate for the TLS
// connection are copied to the thread local storage avaiable in the request,
@@ -1083,5 +1104,51 @@ end-pr;
///
dcl-pr il_setTlsServerCertEnabled extproc(*dclcase);
config likeds(il_config);
- enabled int(5) value;
+ enabled int(3) value;
end-pr;
+
+///
+// Enable client certificate infos in TLS
+//
+// If enabled the available infos about the used client certificate for the TLS
+// connection are copied to the thread local storage avaiable in the request,
+// see il_getThreadMem.
+//
+// The information will be copied to the path /ileastic/certificate/client.
+// The information is only available if the connection uses TLS (HTTPS).
+//
+// @param Configuration
+// @param IL_TRUE = enabled , IL_FALSE = disable (default)
+//
+// @info This setting has no effect if no TLS is used.
+///
+dcl-pr il_setTlsClientCertEnabled extproc(*dclcase);
+ config likeds(il_config);
+ enabled int(3) value;
+end-pr;
+
+///
+// Set client auth mode (mTLS)
+//
+// Sets the client auth mode. The client auth mode defines if the client is asked
+// for a client certificate which will be validated using the configured key
+// store.
+//
+// By default the client is not asked for a certificated (no mTLS).
+//
+// With the configuration set to IL_CLIENT_AUTH_MODE_REQUIRED the client must
+// provide a valid client certificate to establish a TLS connection.
+//
+// With the configuration set to IL_CLIENT_AUTH_MODE_PASSTHRU the client is
+// asked for a client certficate but does not need to provide one.
+//
+// The result of the client certificate validation is provided via the thread
+// local storage at /ileastic/certificate/client/validationcode.
+//
+// @param Configuration
+// @param mTLS client auth mode (see IL_CLIENT_AUTH_MODE_xyz)
+///
+dcl-pr il_setClientAuthMode extproc(*dclcase);
+ config likeds(il_config);
+ mode int(3) value;
+end-pr;
\ No newline at end of file
diff --git a/headers/sysdef.h b/headers/sysdef.h
index 2c79106..92c71c5 100644
--- a/headers/sysdef.h
+++ b/headers/sysdef.h
@@ -46,6 +46,14 @@ typedef enum _THREADING_MODE {
} THREADING_MODE , *PTHREADING_MODE;
#pragma enum (pop)
+#pragma enum (1)
+typedef enum _CLIENT_AUTH_MODE {
+ CLIENT_AUTH_MODE_NONE = 0,
+ CLIENT_AUTH_MODE_REQUIRED = 1,
+ CLIENT_AUTH_MODE_PASSTHRU = 2
+} CLIENT_AUTH_MODE;
+#pragma enum (pop)
+
typedef _Packed struct {
FCGX_Stream * out;
FCGX_Stream * in;
@@ -66,7 +74,9 @@ typedef _Packed struct _CONFIG {
BOOL isWorker;
THREADING_MODE threadingMode;
BOOL tlsServerCertEnabled;
- UCHAR filler[1022];
+ BOOL tlsClientCertEnabled;
+ CLIENT_AUTH_MODE clientAuthMode;
+ UCHAR filler[1021];
// Private:
int mainSocket;
int clientSocket;
diff --git a/itests/ilbasics.rpgle b/itests/ilbasics.rpgle
index cf333e2..8208578 100644
--- a/itests/ilbasics.rpgle
+++ b/itests/ilbasics.rpgle
@@ -81,6 +81,8 @@ dcl-proc main;
endif;
il_setTlsServerCertEnabled(config : IL_TRUE);
+ il_setTlsClientCertEnabled(config : IL_TRUE);
+ il_setClientAuthMode(config : IL_CLIENT_AUTH_MODE_PASSTHRU);
il_addRoute(config : %paddr(test_getBookExcerpt) : IL_GET : REGEX_START + '/book/excerpt' + REGEX_END);
il_addRoute(config : %paddr(test_deleteBook) : IL_DELETE: REGEX_START + '/book/' + CURLY_OPEN + 'isbn' + CURLY_CLOSE + REGEX_END);
@@ -90,6 +92,7 @@ dcl-proc main;
il_addRoute(config : %paddr(test_search) : IL_GET : REGEX_START + '/book' + REGEX_END);
il_addRoute(config : %paddr(test_bookOptions) : IL_OPTIONS : REGEX_START + '/book' + REGEX_END);
il_addRoute(config : %paddr(test_getServerCertInfos) : IL_GET : REGEX_START + '/cert/server' + REGEX_END);
+ il_addRoute(config : %paddr(test_getClientCertInfos) : IL_GET : REGEX_START + '/cert/client' + REGEX_END);
il_addRoute(config : %paddr(test_notFound));
il_listen (config);
@@ -295,3 +298,21 @@ dcl-proc test_getServerCertInfos;
response.contentType = 'application/json';
il_responseWriteStream(response: jx_stream(serverCertInfos));
end-proc;
+
+
+dcl-proc test_getClientCertInfos;
+ dcl-pi *n;
+ request likeds(IL_REQUEST);
+ response likeds(IL_RESPONSE);
+ end-pi;
+
+ dcl-s tls pointer;
+ dcl-s clientCertInfos pointer;
+
+ tls = il_getThreadMem(request);
+ clientCertInfos = jx_locate(tls : '/ileastic/certificate/client');
+
+ response.status = 200;
+ response.contentType = 'application/json';
+ il_responseWriteStream(response: jx_stream(clientCertInfos));
+end-proc;
diff --git a/src/api.c b/src/api.c
index 599d437..336dd9f 100644
--- a/src/api.c
+++ b/src/api.c
@@ -456,3 +456,11 @@ void il_setKeyfile(PCONFIG config, PVARCHAR256 keyfilePath , PVARCHAR64 keyfileP
void il_setTlsServerCertEnabled(PCONFIG config, BOOL enabled) {
config->tlsServerCertEnabled = enabled;
}
+
+void il_setTlsClientCertEnabled(PCONFIG config, BOOL enabled) {
+ config->tlsClientCertEnabled = enabled;
+}
+
+void il_setClientAuthMode(PCONFIG config, CLIENT_AUTH_MODE mode) {
+ config->clientAuthMode = mode;
+}
\ No newline at end of file
diff --git a/src/ileastic.c b/src/ileastic.c
index 9b94c2f..5895383 100644
--- a/src/ileastic.c
+++ b/src/ileastic.c
@@ -65,7 +65,8 @@ BOOL isSecure(PCONFIG config);
void log_gskit_error(const char * msg, int rc);
int setupGskitEnvironment(PCONFIG pConfig);
int setupSecureSocket(PCONFIG pConfig, int clientSocket);
-void addServerCertInfos(gsk_handle * sslHandle, void * threadMem);
+void addCertificateDetails(gsk_handle * sslHandle, void * tlsCertNode, GSK_CERT_ID certificateType);
+void addClientCertificateValidationResult(gsk_handle * sslHandle, void * tlsCertNode);
VARCHAR256 il_getCallingProgramPath();
@@ -816,16 +817,28 @@ static void * serverThread (PINSTANCE pInstance)
PROUTING matchingRouting;
BOOL connected = true;
UCHAR temp [256];
- PVOID serverCertNode;
+ PVOID serverCertNode = NULL;
+ PVOID clientCertNode = NULL;
if(! pInstance->config.isWorker)
{
pthread_detach(pthread_self());
}
- if (isSecure(&(pInstance->config)) && pInstance->config.tlsServerCertEnabled) {
- serverCertNode = jx_NewObject(NULL);
- addServerCertInfos(&(pInstance->config.envHandle), serverCertNode);
+ if (isSecure(&(pInstance->config))) {
+ if (pInstance->config.tlsServerCertEnabled) {
+ serverCertNode = jx_NewObject(NULL);
+ addCertificateDetails(&(pInstance->config.envHandle), serverCertNode, GSK_LOCAL_CERT_INFO);
+ }
+
+ if (pInstance->config.clientAuthMode != CLIENT_AUTH_MODE_NONE) {
+ clientCertNode = jx_NewObject(NULL);
+ addClientCertificateValidationResult(&(pInstance->config.sessionHandle), clientCertNode);
+
+ if (pInstance->config.tlsClientCertEnabled) {
+ addCertificateDetails(&(pInstance->config.sessionHandle), clientCertNode, GSK_PARTNER_CERT_INFO);
+ }
+ }
}
while (pInstance->config.clientSocket > 0 && connected) {
@@ -846,9 +859,16 @@ static void * serverThread (PINSTANCE pInstance)
pResponse = &response;
request.threadMem = (PVOID) jx_NewObject(NULL);
- if (isSecure(&(pInstance->config)) && pInstance->config.tlsServerCertEnabled) {
- PJXNODE tlsCertNode = jx_GetOrCreateNode(request.threadMem, "/ileastic/certificate");
- jx_NodeMoveInto(tlsCertNode, "server", serverCertNode);
+ if (isSecure(&(pInstance->config))) {
+ if (serverCertNode != NULL) {
+ PJXNODE tlsCertNode = jx_GetOrCreateNode(request.threadMem, "/ileastic/certificate");
+ jx_NodeMoveInto(tlsCertNode, "server", serverCertNode);
+ }
+
+ if (clientCertNode != NULL) {
+ PJXNODE tlsCertNode = jx_GetOrCreateNode(request.threadMem, "/ileastic/certificate");
+ jx_NodeMoveInto(tlsCertNode, "client", clientCertNode);
+ }
}
#pragma exception_handler(handleServletException, pResponse, _C1_ALL, _C2_MH_ESCAPE, _CTLA_HANDLE)
@@ -1280,7 +1300,7 @@ void il_listen (PCONFIG pConfig, SERVLET servlet)
}
}
-void addServerCertInfos(gsk_handle * sslHandle, void * serverCertInfos) {
+void addCertificateDetails(gsk_handle * sslHandle, void * tlsCertNode, GSK_CERT_ID certificateType) {
// All certificate data is returned in ASCII CCSID 850.
PXLATEDESC a2e = XlateXdOpen(850, 0);
@@ -1292,13 +1312,11 @@ void addServerCertInfos(gsk_handle * sslHandle, void * serverCertInfos) {
rc = gsk_attribute_get_cert_info (
*sslHandle,
- GSK_LOCAL_CERT_INFO,
+ certificateType,
&certElements,
&certElementCount);
if (rc == GSK_OK) {
- il_joblog("got cert infos: %d", certElementCount);
-
// typedef struct gsk_cert_data_elem_t {
// GSK_CERT_DATA_ID cert_data_id;
// char *cert_data_p;
@@ -1458,7 +1476,7 @@ void addServerCertInfos(gsk_handle * sslHandle, void * serverCertInfos) {
break;
}
- if (certInfoKey) jx_SetStrByName (serverCertInfos, certInfoKey, certInfoValue);
+ if (certInfoKey) jx_SetStrByName (tlsCertNode, certInfoKey, certInfoValue);
}
} else {
@@ -1468,6 +1486,15 @@ void addServerCertInfos(gsk_handle * sslHandle, void * serverCertInfos) {
XlateXdClose(a2e);
}
+void addClientCertificateValidationResult(gsk_handle * sslHandle, void * tlsCertNode) {
+ int value = 0;
+ int rc = gsk_attribute_get_numeric_value(*sslHandle, GSK_CERTIFICATE_VALIDATION_CODE, &value);
+ if (rc == GSK_OK)
+ jx_SetIntByName(tlsCertNode , "validationcode", value);
+ else
+ log_gskit_error("Get client certificate validation code", rc);
+}
+
BOOL isSecure(PCONFIG config) {
BOOL secured = config->certificateFile.Length > 0;
return secured;
@@ -1524,35 +1551,46 @@ int setupGskitEnvironment(PCONFIG pConfig) {
}
int setupSecureSocket(PCONFIG pConfig, int clientSocket) {
- int rc;
-
- // Create secure socket handle
- rc = gsk_secure_soc_open(pConfig->envHandle, &(pConfig->sessionHandle));
- if (rc != GSK_OK) {
- log_gskit_error("gsk_secure_soc_open", rc);
- return -1;
- }
-
- // Associate socket with SSL handle
- rc = gsk_attribute_set_numeric_value(pConfig->sessionHandle, GSK_FD, clientSocket);
- if (rc != GSK_OK) {
- log_gskit_error("gsk_attribute_set_numeric_value GSK_FD", rc);
- return -1;
- }
-
- // Set as server socket
- rc = gsk_attribute_set_enum(pConfig->sessionHandle, GSK_SESSION_TYPE, GSK_SERVER_SESSION);
- if (rc != GSK_OK) {
- log_gskit_error("gsk_attribute_set_enum GSK_SESSION_TYPE", rc);
- return -1;
- }
-
- // Initialize secure session
- rc = gsk_secure_soc_init(pConfig->sessionHandle);
- if (rc != GSK_OK) {
- log_gskit_error("gsk_secure_soc_init", rc);
- return -1;
- }
-
- return 0;
+ int rc;
+
+ // Create secure socket handle
+ rc = gsk_secure_soc_open(pConfig->envHandle, &(pConfig->sessionHandle));
+ if (rc != GSK_OK) {
+ log_gskit_error("gsk_secure_soc_open", rc);
+ return -1;
+ }
+
+ // Associate socket with SSL handle
+ rc = gsk_attribute_set_numeric_value(pConfig->sessionHandle, GSK_FD, clientSocket);
+ if (rc != GSK_OK) {
+ log_gskit_error("gsk_attribute_set_numeric_value GSK_FD", rc);
+ return -1;
+ }
+
+ // Set as server socket
+ GSK_ENUM_ID sessionType = pConfig->clientAuthMode != CLIENT_AUTH_MODE_NONE ? GSK_SERVER_SESSION_WITH_CL_AUTH : GSK_SERVER_SESSION;
+ rc = gsk_attribute_set_enum(pConfig->sessionHandle, GSK_SESSION_TYPE, sessionType);
+ if (rc != GSK_OK) {
+ log_gskit_error("gsk_attribute_set_enum GSK_SESSION_TYPE", rc);
+ return -1;
+ }
+
+ if (pConfig->clientAuthMode != CLIENT_AUTH_MODE_NONE) {
+ GSK_ENUM_ID authType = pConfig->clientAuthMode == CLIENT_AUTH_MODE_PASSTHRU ? GSK_CLIENT_AUTH_PASSTHRU : GSK_IBMI_CLIENT_AUTH_REQUIRED;
+
+ gsk_attribute_set_enum(pConfig->sessionHandle, GSK_CLIENT_AUTH_TYPE , authType );
+ if (rc != GSK_OK) {
+ log_gskit_error("gsk_attribute_set_enum GSK_CLIENT_AUTH_TYPE", rc);
+ return -1;
+ }
+ }
+
+ // Initialize secure session
+ rc = gsk_secure_soc_init(pConfig->sessionHandle);
+ if (rc != GSK_OK) {
+ log_gskit_error("gsk_secure_soc_init", rc);
+ return -1;
+ }
+
+ return 0;
}
\ No newline at end of file