Skip to content

adding JWT enablement to C samples #306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 31 additions & 9 deletions C/Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
COMMONSRC = common.c config.c
COMMONINC = common.h config.h
COMMONINC = common.h config.h

MQINC=/opt/mqm/inc
MQLIB=/opt/mqm/lib64
Expand All @@ -11,7 +11,30 @@ APPS = sampleput \
samplepublish \
samplesubscribe

# CDEBUG=-g
CDEBUG=-g



ifndef CURL_INCLUDE
CURL_INCLUDE := /opt/homebrew/opt/curl/include
endif

ifndef JSONC_INCLUDE
JSONC_INCLUDE := /opt/homebrew/opt/json-c/include
endif

ifndef CURL_LIB
CURL_LIB := /opt/homebrew/opt/curl/lib
endif

ifndef JSONC_LIB
JSONC_LIB := /opt/homebrew/opt/json-c/lib
endif

ifeq ($(JWT),1)
JWT_CFLAGS += -DJWT_ENABLED -I$(CURL_INCLUDE) -I$(JSONC_INCLUDE)
JWT_LDFLAGS += -L$(CURL_LIB) -lcurl -L$(JSONC_LIB) -ljson-c
endif

all: $(APPS)
@rm -rf *.dSYM
Expand All @@ -22,19 +45,18 @@ clean:
@rm -f *.exe *.obj

sampleput: sampleput.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r

$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)
sampleget: sampleget.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)

samplerequest: samplerequest.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)

sampleresponse: sampleresponse.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)

samplepublish: samplepublish.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)

samplesubscribe: samplesubscribe.c $(COMMONSRC) $(COMMONINC) Makefile
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r
$(CC) $(CDEBUG) -o $@ $@.c $(COMMONSRC) -I$(MQINC) -L$(MQLIB) -lmqm_r $(JWT_CFLAGS) $(JWT_LDFLAGS)
78 changes: 77 additions & 1 deletion C/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ These samples use the C MQI to demonstrate basic messaging operations.
* Install the IBM MQ client for your system, or unpack the Redistributable Client package if available.
* The SDK component is needed in order to compile these programs.
* You also need a C compiler
* And the 'make' build automation tool

## Introduction to the C samples

Expand Down Expand Up @@ -59,26 +60,101 @@ Apart from the optional command line parameter naming the configuration file, th
other parameters to any of the programs.

You might need to run `setmqenv` to create environment variables pointing at your MQ installation
libraries. And on MacOS, the `DYLD_LIBRARY_PATH` will usually need to be set to include the
libraries.

And on MacOS, the `DYLD_LIBRARY_PATH` will usually need to be set to include the
`/opt/mqm/lib64` directory.

`export DYLD_LIBRARY_PATH=/opt/mqm/lib64`

If you are on Linux, you might need set the `LD_LIBRARY_PATH` to include the `/opt/mqm/lib64` directory.

`export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/mqm/lib64`

See [here](https://www.ibm.com/docs/en/ibm-mq/latest?topic=reference-setmqenv-set-mq-environment) for
more information about `setmqenv`.

### Put/Get
The `sampleput` application places a short string message onto the queue.

`./sampleput`

The `sampleget` application reads all messages from the queue and displays the contents.

`./sampleget`

### Publish/Subscribe
Run these samples as a pair.

Start the `samplesubcribe` program in one window (or in the background) and immediately afterwards start the
`samplepublish` program in another window.

`./samplesubscribe`

`./samplepublish`

### Request/Response
Run these samples as a pair.

Start the `sampleresponse` program in one window (or in the background) and immediately afterwards start the
`samplerequest` program in another window.

`./sampleresponse`

`./samplerequest`

### Running samples with JWT authentication

To enable token-based authentication, ensure you have a configured token issuer and queue manager [JWT README](jwt-jwks-docs/README.md) and then edit the `JWT_ISSUER` block in the env.json file

```JSON
"JWT_ISSUER" : [{
"JWT_TOKEN_ENDPOINT":"https://<KEYCLOAK_URL>/realms/master/protocol/openid-connect/token",
"JWT_TOKEN_USERNAME":"app",
"JWT_TOKEN_PWD":"passw0rd",
"JWT_TOKEN_CLIENTID":"admin-cli",
"JWT_KEY_REPOSITORY": "path/to/tokenIssuerKeystore"
}]
```
For JWT authentication via JWKS, make sure `JWT_KEY_REPOSITORY` points to your token issuer's public certificate and your queue manager is configured to retrieve the JWKS.

If you would like to proceed with JWT authentication without JWKS validation, edit the endpoint to use the correct URL and leave `JWT_KEY_REPOSITORY` blank.


Before you compile and run the samples ensure you have installed the required curl and json-c libraries. This can be done through homebrew:

`brew install curl`
`brew install json-c`

or you can visit the following websites:
[curl](https://curl.se/docs/install.html)
[json-c](https://github.com/json-c/json-c)

And on MacOS, the `DYLD_LIBRARY_PATH` will need to be set to include the curl library directory.

`export DYLD_LIBRARY_PATH=/opt/homebrew/opt/curl/lib:/opt/mqm/lib64`

And on Linux, the `LD_LIBRARY_PATH` will also need to be set accordingly.

`export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/linuxbrew/.linuxbrew/Cellar/curl/lib:/opt/mqm/lib64`

*note: These commands use the homebrew install path, if you installed curl/json-c via another method, edit the path as required.

To compile a sample with JWT enablement:

If you have samples that you have already compiled, make sure to get rid of these executable files by running:

`make clean`

If you are on a Mac and have installed the curl and json-c libraries through homebrew, you can compile your applications by simply running:
`make JWT=1`

Our makefile defaults the library directories to the homebrew MacOS path, so if you are on a different machine or have gone through an alternative installation method - you will need to point the makefile to the correct directories:

`make JWT=1 \
CURL_INCLUDE=/custom/path/to/curl/include \
CURL_LIB=/custom/path/to/curl/lib \
JSONC_INCLUDE=/custom/path/to/json-c/include \
JSONC_LIB=/custom/path/to/json-c/lib`

Then you can run the samples as normal.
149 changes: 144 additions & 5 deletions C/common.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2024 IBM Corp.
* Copyright 2024,2025 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
Expand All @@ -17,6 +17,12 @@
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

#ifdef JWT_ENABLED
#include <curl/curl.h>
#include <json-c/json.h>
#endif

#include <cmqc.h>
#include <cmqstrc.h>
Expand Down Expand Up @@ -46,6 +52,7 @@ int connectQMgr(PMQHCONN pHConn) {
int s = sizeof(ConnectionName);

mqEndpoint_t ep = mqEndpoints[0];
jwtEndpoint_t jwtEp = jwt;

// Set structure version high enough to include all the fields we might want to use
mqcno.Version = MQCNO_VERSION_8;
Expand Down Expand Up @@ -119,9 +126,29 @@ int connectQMgr(PMQHCONN pHConn) {

}

// Authentication can apply for both local and client connections
// Using JWT tokens would require code to actually get the token from
// a server first, so that's not going in here for now.
#ifdef JWT_ENABLED

if (jwtCheck(jwtEp)) {

char *token = obtainToken(jwtEp);

if (!token) {
fprintf(stderr, "Failed to obtain token — exiting.\n");
return rc;
}

printf("Using token:\n%s\n", token);

mqcsp.Version = MQCSP_VERSION_3;
mqcsp.TokenPtr = token;
mqcsp.AuthenticationType = MQCSP_AUTH_ID_TOKEN;
mqcsp.TokenLength = (MQLONG) strlen(token);
mqcno.SecurityParmsPtr = &mqcsp;

}
else
#endif

if (ep.appUser) {
mqcsp.CSPUserIdPtr = ep.appUser;
mqcsp.CSPUserIdLength = strlen(ep.appUser);
Expand Down Expand Up @@ -230,4 +257,116 @@ void dumpHex(const char *title, void *buf, int length) {
}

return;
}
}

#ifdef JWT_ENABLED
// check for any missing JWT credentials
int jwtCheck(jwtEndpoint_t jwtEp){

if (!jwtEp.tokenEndpoint || !jwtEp.tokenUserName || !jwtEp.tokenPwd || !jwtEp.tokenClientId) {
printf("One or more JWT credentials missing, will not be using JWT to authenticate\n");
return 0;

}else{
printf("JWT credentials found, will be using JWT to authenticate\n");
}
return 1;
}

// callback function to write response from curl request into memory
size_t write_chunk(void *data, size_t size, size_t nmemb, void *userdata){

size_t totalSize = size * nmemb;

jwtResponse *response = (jwtResponse *) userdata;

char *ptr = realloc(response->string, response->size + totalSize + 1);

if (ptr == NULL){
return CURL_WRITEFUNC_ERROR;
}

response->string = ptr;
memcpy(&(response->string[response->size]), data, totalSize);
response->size += totalSize;
response->string[response->size] = 0;

return totalSize;
}

// use the libCurl library to obtain token
// use the json-c library to parse the response and extract access token
char* obtainToken(jwtEndpoint_t jwtEp) {

CURL *curl;
CURLcode result;
char post_data[512];
struct json_object *parsed_json = NULL;
struct json_object *accessToken = NULL;
char *token = NULL;

snprintf(post_data, sizeof(post_data),
"username=%s&password=%s&client_id=%s&grant_type=password",
jwtEp.tokenUserName, jwtEp.tokenPwd, jwtEp.tokenClientId);

curl = curl_easy_init();
if (curl == NULL){
fprintf(stderr, "request failed\n");
return NULL;
}

jwtResponse response;
response.string = malloc(1);
response.size = 0;

curl_easy_setopt(curl, CURLOPT_URL, jwtEp.tokenEndpoint);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_chunk);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &response);

// if the token issuer is a https server, access the server's public certificate
// JWT_KEY_REPOSITORY must point to the server's public certificate
if (jwtEp.tokenKeyRepository) {
curl_easy_setopt(curl, CURLOPT_CAINFO, jwtEp.tokenKeyRepository);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

// uncomment the following command to debug https request
//curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
}

result = curl_easy_perform(curl);

if (result != CURLE_OK){
fprintf(stderr, "Error: %s\n", curl_easy_strerror(result));
curl_easy_cleanup(curl);
free(response.string);
return NULL;
}

curl_easy_cleanup(curl);


parsed_json = json_tokener_parse(response.string);
if (!parsed_json) {
fprintf(stderr, "Failed to parse JSON response\n");
free(response.string);
return NULL;
}

if (!json_object_object_get_ex(parsed_json, "access_token", &accessToken)) {
fprintf(stderr, "JSON does not contain 'access_token'\n");
json_object_put(parsed_json);
free(response.string);
return NULL;
}

const char *temp = json_object_get_string(accessToken);
token = strdup((char *)temp);

json_object_put(parsed_json);

free(response.string);

return token;
}
#endif
8 changes: 7 additions & 1 deletion C/common.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2024 IBM Corp.
* Copyright 2024,2025 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
Expand All @@ -19,6 +19,8 @@

#include <cmqc.h>

#include "config.h"

int connectQMgr(PMQHCONN);
void disconnectQMgr(PMQHCONN);

Expand All @@ -28,6 +30,10 @@ void closeObject(MQHCONN hConn, PMQHOBJ pHObj);
void printError(char *verb, MQLONG compCode, MQLONG reason);
void dumpHex(const char *title, void *buf, int length);

int jwtCheck(jwtEndpoint_t endpoint);
size_t write_chunk(void *data, size_t size, size_t nmemb, void *userdata);
char* obtainToken(jwtEndpoint_t endpoint);

#define DEFAULT_BUFFER_LENGTH 256

#endif
Loading