What’s the best way of connecting to an HTTPS server. Pass ticket or JWT?

This blog post was written as background to some blog posts on Zowe API-ML. It provides back ground knowledge for HTTPS servers running on z/OS, and I think it is useful on its own. Ive written about an MQWEB server – because I have configured this on my system.

The problem

I want to manage my z/OS queue manager from my Linux machine.I have several ways of doing it.

Which architecture?

  • Use an MQ client. Establish a client connect to the CHINIT, and use MQPUT and MQGET administration messages to the queue manager.
    • You can issue a command string, and get back a response string which you then have to parse
    • You can issue an MQINQ API request to programmatically query attributes, and get the values back in fields. No parsing, but you have to write a program to do the work.
  • Use the REST API. This is an HTTP request in a standard format into the MQWEB server.
    • You can issue a command string, and get back a response string which you then have to parse to extract the values.
    • You can issue a JSON object where the request is encoded in a URL, and get the response back in JSON format. It is trivial to extract individual fields from the returned data.

Connecting to the MQWEB server

If you use REST (over HTTPS) there are several ways of doing this

  • You can connect using userid and password. It may be OK to enter your password when you are at the keyboard, but not if you are using scripts and you may be away from your keyboard. If hackers get hold of the password, they have weeks to use it, before the password expires. You want to give your password once per session, not for every request.
  • You can connect using certificates, without specifying userid and password.
    • It needs a bit of set up at the server to map your certificate to a userid.
    • It takes some work to set up how to revoke your access, if you leave the company, or the certificate is compromised.
    • Your private key could be copied and used by hackers. There is discussion about reducing the validity period from over a year to 47 days. For some people this is still too long! You can have your private certificate on a dongle which you have to present when connecting to a back end. This reduces the risk of hackers using your private key.
  • You can connect with a both certificate and userid and password. The certificate is used to establish the TLS session, and the userid and password are used to logon to the application.
  • You can use a pass ticket. You issue a z/OS service which, if authorised, generates a one time password valid for 10 minutes or less. If hackers get hold of the pass ticket, they do not have long to be able to exploit it. The application generating the pass ticket, does not need the password of the userid, because the application has been set up as trusted.
  • You can use a JSON Web Token (JWT). This has some similarities with certificates. In the payload is a userid value and issuer value . I think of issuer as the domain the JWT has come from – it could be TEST or a company name. From the issuer value, and IP address range, you configure the server to specify a realm value. From the userid and realm you can map this to a userid on the server. This JWT can be valid from minutes to many hours (but under a day). The userid and realm mapping to a userid is different to certificate mapping to a userid.

Setting up a pass ticket

The passticket is used within the sysplex. It cannot be used outside of a sysplex. The pass ticket is a password – so needs to be validated against the RACF database.

The application that generates the pass ticket must be authorised to a profile for the application. For example, define the profile for the application TSO on system S0W1, the profile is TSOS0W1.

 RDEFINE PTKTDATA TSOS0W1 

and a profile to allow a userid to create a pass ticket for the application

RDEFINE PTKTDATA   IRRPTAUTH.TSOS0W1.*  UACC(NONE) 

PERMIT IRRPTAUTH.TSOS0W1.* CLASS(PTKTDATA) ID(COLIN) ACCESS(UPDATE)
PERMIT IRRPTAUTH.TSOS0W1.* CLASS(PTKTDATA) ID(IBMUSER)ACCESS(UPDATE)

Userids COLIN and IBMUSER can issue the callable service IRRSPK00 to generate a pass ticket for a user for the application TSOS0W1.

The output is a one-use password which has a validity of up to 10 minutes.

As an example, you could configure your MQWEB server to use profile name MQWEB, or CSQ9WEB.

How is it used

A typical scenario is for an application running on a work station to issue a request to an “application” on z/OS, like z/OSMF, to generate a pass ticket for a userid and application name.

The client on the work station then issues a request to the back end server, with the userid and pass ticket. If the back end server matches the application name then the pass ticket will be accepted as a password. The logon will fail if a different application is used, so a pass ticket for TSO cannot be used for MQWEB.
This is more secure than sending a userid and password up with every back end request, but there is additional work in creating the pass ticket, and two network flows.

This solution scales because very little work needs to be done on the work station, and there is some one-off work for the setup to generate the pass tickets.

JSON Web Tokens

See What are JSON Web Tokens and how do they work?

The JWT sent from the client has an expiry time. This can be from seconds to hours. I think it should be less than a day – perhaps a couple of hours at most. If a hacker has a copy of the JWT, they can use it until it expires.

The back end server needs to authenticate the token. It could do this by having a copy of the public certificate in the server’s keyring, or send a request down to the originator to validate it.

If validation is being done with public certificates, because the client’s private key is used to generate the JWT, the server needs a copy of the public certificate in the server’s keyring. This can make it hard to manage if there are many clients.

The Liberty web server has definitions like

<openidConnectClient id="RSCOOKIE" 
clientId="COLINCOO2"
realmName="zOSMF"
inboundPropagation="supported"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="false"
signatureAlgorithm="RS384"
trustAliasName="CONN1.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
>
<authFilter id="afint">
<remoteAddress id="myAddress" ip="10.1.0.2" matchType="equals" />
</authFilter >

</openidConnectClient>

For this entry to be used various parameters need to match

  • The issuerIdentifier. This string identifies the client. It could be MEGABANK, TEST, or another string of your choice. It has to match what is in the JWT.
  • signatureAlgorithm. This matches the incoming JWT.
  • trustAliasName and trustStoreRef. These identify the certificate used to validate the certificate
  • remoteAddress. This is the address, or address range of the client’s IP addresses.

If you have 1000 client machines, you may need 1000 <openidConnectClient…/> definitions, because of the different certificate and IP addresses.

You may need 1000 entries in the RACMAP mapping of userid + realm to userid to be used on the server.

How is it used

You generate the JWT. There are different ways of doing this.

  • Use a service like z/OSMF
  • Use a service on your work station. I have used Python to do this. The program is 30 lines long and uses the Python jwt package

You get back a long string. You can see what is in the string by pasting the JWT in to jwt.io.
You pass this to the backend as a cookie. The cookie name depends on what the server is expecting. For example

'Authorization': "Bearer " + token

The JWT has limited access

For the server to use the JWT, it needs definitions to recognise it. If you have two back end servers

  • Both servers could be configured to accept the JWT
    • If the server specified a different REALM, then the mapped userid from the JWT could be different for each server because the userid/realm to userid mapping can be different.
  • One server is configured to accept the JWT
    • If only one server has the definitions for the JWT, then trying to use the JWT to logon to another server will fail.

Configure the mqweb server to accept JWT.

See my blog post JWT to learn what JSON Web Token are, and how they work.

In Liberty the JWT is processed by OpenID Connect Client.

In my mqwebuser.xml I had

<featureManager> 
<feature>transportSecurity-1.0</feature>
<feature>openidConnectClient-1.0</feature>
</featureManager>
<openidConnectClient
id="RS2"
clientId="COLINSOC"
jwkEndpointUrl="https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk"
inboundPropagation="supported"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="true"
signatureAlgorithm="RS384"
trustAliasName="CONN2.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
/>

Where

  • id=”RS2″ is any label
  • clientId=”COLINSOC” is another label
  • jwkEndpointUrl=”https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk&#8221;
  • inboundPropagation=”required”
  • issuerIdentifier=”zOSMF” this matches the iss-uer in the JWT token
  • mapIdentityToRegistryUser=”false” this is to use the Liberty userid mapping so specify false to use the RACF mapping.
  • signatureAlgorithm=”RS384″ – this has to match what is in the task that creates the JWT. If I had RS256 – it came out as Elliptic Curve. I configued z/OSMF and MQWEB both to use RS384 and it worked.
  • trustAliasName=”CONN2.IZUDFLT” the certificate to use in the validation
  • trustStoreRef=”defaultKeyStore” this point to the definition of the trust keystore to use
  • userIdentifier=”sub” the user name is taken from this field in the JWT

The JWT had

header

{
"kid": "aaPdG7y6fDNNTMCT6wb9-Oe21M63dPS3MtCeF7kYKn8",
"typ": "JWT",
"alg": "RS384"
}

The payload had

token_type:Bearer
sub: IBMUSER The subject of the JWT (the user).
upn: IBMUSER
groups["DBBADMNS","IZUADMIN","IZUUSER","PKIGRP","SYS1","ZWEADMIN"]
realm:SAFRealm
iss:zOSMF The issuer of the JWT.
exp: 1754240783 (Sun Aug 03 2025 18:06:23 GMT+0100 (British Summer Time)). The expiration time on or after which the JWT MUST NOT be accepted for processing. Learn more
iat:1754237783 (Sun Aug 03 2025 17:16:23 GMT+0100 (British Summer Time)) The time at which the JWT was issued.

Getting it to work

I used Setting mqweb trace on z/OS and other useful hints on tracing extensively.

Using JWT and when it goes wrong has some debugging hints.

Processing lines in ASCII files in ISPF edit macros made looking at log files so much easier, by displaying lines from a trace file on one screen – rather than having to scroll sideways many times.

Using JWT and when it goes wrong

General

In the Liberty traces, I tended to look for the last few CWW…. messages.

Processing lines in ASCII files in ISPF edit macros made looking at log files so much easier.

Tracing the openidConnectClient activity

You can use the trace

com.ibm.ws.security.*=all:com.ibm.ws.webcontainer.security.*=all:com.ibm.oauth.*=all:com.ibm.wsspi.security.oauth20.*=all:org.openid4java.*=all:org.apache.http.client.*=all:io.openliberty.security.*=all

to get a lot of information about the activity.

  • com.ibm.oauth.*=all didnt give me anything.
  • com.ibm.ws.webcontainer.security.*=fine didn’t produce anything
  • com.ibm.ws.webcontainer.security.*=finer produced good stuff – too much info

I used Setting mqweb trace on z/OS and other useful hints on tracing extensively to look at the Liberty traces.

Messages

CWWKS1776E: Validation failed for the token requested by (COLINCOO2) using the (RS384) algorithm due to a signature verification failure:

CWWKS1737E: The OpenID Connect client (COLINCOO2) failed to validate the JSON Web Token. The cause of the error was: (JWT rejected due to invalid signature).
After I added the certificate to the keyring, I needed to restart the server to pickup the change.

CWWKS2915E: SAF service IRRSIA00_CREATE did not succeed because group
null was not found in the SAF registry. SAF return code 0x00000008. RACF return code 0x00000008. RACF reason code 0x00000010.

Explanation: The JWT has a userid, and the userid/realm mapping does not exist in the RACMAP definitions. I think this is a bug… it should not have got into RRSIA00_CREATE if there is no userid.

Basic configuration errors

When there was no matching issuerIdentifier in the openidConnectClient, I got

HTTP/2 401
www-authenticate: Bearer realm=”jwt”, error=”invalid_token”, error_description=”Check JWT token”

{“error_description”:”OpenID Connect client returned with status: SEND_401″,”error”:401}

With the above I got in the trace

… Jose4jUtil E CWWKS1737E: The OpenID Connect client (…) failed to validate the JSON Web Token . The cause of the error was: (
CWWKS1773E: Validation failed for the token requested by the (…) OpenID Connect client for the (…) user because the token is outside of its valid range. This error occurs either because the (2025-08-08T18:45:15.182Z) current time is after the (2025-08-08T18:03:21.000Z) token expiration time or because the (2025-08-08T17:13:21.000Z) issue time is too far away from the (2025-08-08T18:45:15.182Z) current time.)

Which means the token has expired.

Using a Python script to access MQWEB with JSON Web Tokens

See JWT for my blog post on what JWT are and how they work.

I also gave myself the additional challenge of not saving sensitive information in disk files.

Once I had got the basics working using a Bash script, I used Python as a proper solution, because I could capture the information from the requests much easier.

Overall application

My overall application is

Python

Get the JWT

#!/usr/bin/env python3
from timeit import default_timer as timer
import ssl

#import time
#import base64
#import json
import sys
from http.client import HTTPConnection # py3
import requests
import urllib3
# trace the traffic flow
HTTPConnection.debuglevel = 1

my_header = { 'Accept' : 'application/json' }

urllib3.disable_warnings()

geturl = "https://10.1.1.2:10443/zosmf/services/authenticate"

context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)

certificate="colinpaice.pem"
key="colinpaice.key.pem"
cpcert=(certificate,key)

jar = requests.cookies.RequestsCookieJar()

caCert='./doczosca.pem'

s = requests.Session()
res = s.post(geturl,headers=my_header,cookies=jar,cert=cpcert,verify=caCert)

if res.status_code != 200:
print(res.status_code)
#headers = res.headers
#print("Header",type(headers))
#for h in headers:
# print(h,headers[h])

cookies = res.cookies.get_dict()
token=""
for c in cookies:
print("cookie",c,cookies[c])
if c == "jwtToken":
token = cookies[c]

if token == "" :
print("No jwtToken cookie returned ")
sys.exit(8)

Issue the MQ command

print("===========NOW DO MQ ==============")
mqurl="https://10.1.1.2:9443/ibmmq/rest/v1/admin/action/qmgr/CSQ9/mqsc"
tok = "Bearer " + token
mq_header = {
'Accept' : 'application/json',
'Authorization' : tok,
'Content-Type': 'application/json',
'ibm-mq-rest-csrf-token' : ''
}

data={"type": "runCommand",
"parameters": {"command": "DIS QMGR ALL"}}

mqres = s.post(mqurl,headers=mq_header,cookies=jar,verify=False,json=data)

print("==MQRES",mqres)
print("mqheader",mqres.headers )
print("mqtext",mqres.text)

sys.exit(0)

Notes:

  • The authorisation token is created by “Beader ” concatenated from the jwtToken value.
  • The data is created as json. {“type”: “runCommand”,….}. It needs header ‘Content-Type’: ‘application/json’,

Using a Bash script to access MQWEB with JSON Web Tokens

See JWT for my blog post on what JWT are and how they work.

I also gave myself the additional challenge of not saving sensitive information in disk files.

Once I got the scripts working I used a Python script- which was much easier to use.

Overall application

My overall application is

BASH

I initially tried using a BASH script for creating and using JWT to issue MQ REST API requests to MQWEB.

This worked, but capturing the JWT from the cookie was not easy to implement.

Get the JWT

#!/bin/bash
rm cookie.jar.txt

url="https://10.1.1.2:10443/zosmf/services/authenticate"
tls="--cacert doczosca.pem --tlsv1.2 --tls-max 1.2"
certs=" --cert ./colinpaice.pem:password --key ./colinpaice.key.pem"
insecure="--insecure"
cj="--cookie cookie.jar.txt --cookie-jar cookie.jar.txt"

curl -v $cj $tls $certs $url $insecure

Note: If there was a valid JWT in the cookie store, the code did not return a JWT. I deleted the cookie file to get round this.

Issue the MQ command

#!/bin/bash
set -x

url="https://10.1.1.2:9443/ibmmq/rest/v1/admin/action/qmgr/CSQ9/mqsc"

token="..."

tls="--cacert ./doczosca.pem --tlsv1.2"
certca="--cacert ./doczosca.pem "

origin="-H Origin:"
post="-X POST"
# need --insecure to avoid subjectAltName does not match
insecure="--insecure"

cj="--cookie cookie.jar.txt --cookie-jar cookie.jar.txt"

curl --verbose -H "Authorization: Bearer $token" -H "Connection: close" $cj $header $insecure $verify $tls -H "Content-Type: application/json" -H "ibm-mq-rest-csrf-token: value" $certs $trace $url --data "{ \"type\": \"runCommand\", \"parameters\": {\"command\": \"DIS QMGR ALL\"} }"

I used cut and paste to copy the JWT from the output of the CURL z/OSMF request, and paste it in token=”” in the MQ script.
I did this because my BASH scripting was not up trying to getting the JWT from the z/OSMF script.

How to configure systems to use JSON Web Token

JSON Web Token(JWT) is a technique of authenticating across systems. It depends on trust, and technology similar to TLS and SSL.

The high level picture

A client wants to use a service on a server.

On the client machine

  • The client creates a payload containing authentication information, such as subject(=userid), issuer(=machine/company name), issued time, and expiry time.
  • This payload is signed using a private key. Where signing is doing a checksum on the payload and encrypted the checksum with the private key.
  • The payload and signature are sent to the server.

On the server machine

  • The server has a list of definitions, containing a issuer and other information, the name of a keyring, and the name of a certificate. The trust comes from if you trust the client then you store the public key for the client in the keyring. If you do not trust the client, you do not make the certificate available.
  • When the data from the client machine arrives, the list of definitions is processed until a match is found. It checks the issuer and other fields, (it can use filters) and if they match, checks the signature of the payload to find the public key in the keyring, and validates the signature.
  • If everything matches, information from the payload and the definition on the server are used to look up a list of subject, and issuer to get the RACF userid to be used.
  • The thread switches to the userid and does the application work.

How to create a JWT

  • z/OSMF can do this for you
  • You can create your own on z/OS – but you need to know how to do a checksum and encrypt with a private key. ICSF provides services which do this.
  • use Python.

Use z/OSMF

In the logical server.xml file

<featureManager> 
<feature>jwtSso-1.0</feature>
</featureManager>

<jwtSso cookieName="jwtToken"
jwtBuilderRef="zOSMFBuilder"
includeLtpaCookie="true"
useLtpaIfJwtAbsent="true"
/>

<jwtBuilder id="zOSMFBuilder"
issuer="zOSMF"
keyAlias="CONN2.IZUDFLT"
expiresInSeconds="10"
jwkEnabled="false"
signatureAlgorithm="RS384"
/>

<mpJwt id="myMpJwt"
issuer="zOSMF"
wksUri="https://${izu.jwks.hostname}:${izu.https.port}/jwt/ibm/api/zOSMFBuilder/jwk"
signatureAlgorithm="RS384"
/>

Interesting fields are

  • issuer=”zOSMF” this is put into the payload. It is used to help identify the realm
  • keyAlias=”CONN2.IZUDFLT”. This is the certificate with the private key in the keyring which is used to sign the JWT
  • expiresInSeconds=”10″ how long the JWT is valid for. During testing I set this to 600 seconds.
  • jwksUri=”https://${izu.jwks.hostname}:${izu.https.port}/jwt/ibm/api/zOSMFBuilder/jwk” if you want the server to issue a request to the client asking for validation, this is the URL the server would use.

Use Python

This is very easy

from datetime import datetime, timezone, timedelta
import jwt

# open the private key and read the contents
pemfile = open("/home/colinpaice/ssl/ssl2/colinpaice.key.pem", 'r')
keystring = pemfile.read()
pemfile.close()

# Create the header. Specify the type, and the encryption algorithm
header= {
"typ": "JWT",
"alg": "RS256"
}

# get time in number of seconds since "day 0"
now = datetime.now(timezone.utc)
payload = {
"token_type": "Bearer",
"sub": "ADCDC", # userid
"upn": "ADCDC",
"realm": "PYTHON",
"iss": "PYTHON", # issuer
"iat": now,
"exp": now + timedelta(seconds=6000) # valid for 6000 seconds

}

# encrypt it and create the jwt
token = jwt.encode(payload, keystring, algorithm='RS256')

print(token) # display it

Backend server set up

The page Configuration example: Securing a Liberty web application with a JWT and CICS transaction security explains how to do it for Liberty in a CICS region.

For my MQWEB server I added to mqwebuser.xml

<featureManager> 
<feature>openidConnectClient-1.0</feature>
</featureManager>

<openidConnectClient id="RSCOOKIE"
clientId="COLINCOOK"
realmName="zOSMF"
inboundPropagation="required"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="false"
signatureAlgorithm="RS384"
jwkEndpointUrl="https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk"
trustAliasName="CONN2.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
>
</openidConnectClient>

mapIdentityToRegistryUser=”false” says use the realm in this definition and the userid(subject) from the payload to look up the in the RACF RACMAP to get the userid.

If you specify “true” it uses the userid(subject) from the payload, and the sysplex name. This means a userid “COLIN” from a z/OS system, and a userid “COLIN” from Linux – get the same userid on z/OS.

Note: If they public key for the JWT is not in the keyring, add it, and restart the server.

Map subject(userid) and realm to get a userid.

See RACMAP (Create, delete, list, or query a distributed identity filter)

For example

RACMAP ID(IBMUSER) MAP USERIDFILTERNAME("IBMUSER")  REGISTRY("zPROD") WITHLABEL("zPROD")
RACMAP ID(NOONE) MAP USERIDFILTERNAME("IBMUSER") REGISTRY("LINUX") WITHLABEL("LINUX")
RACMAP ID(ZILTCH) MAP USERIDFILTERNAME("*") REGISTRY("*") WITHLABEL("CATCHALL")

The registry is the realm name from the server definitions.

if a valid userid is returned from the mapping, the thread is changed to run as that userid, and execute the application work – as that userid.

The backend server is a little more complex

You need a <openidConnectClient ../> for each “client” system the server supports.

You can specify one <authFilter../> to restrict what the <openidConnectClient ../> processes. For example if can be an IP address(10.1.0.2) or a range (10.1.*.*), or restrict it by URL.

For the <openidConnectClient ../> definition to match it needs

  • The signature validated – so the public key needs to be in a keyring. (You could have a different keyring for each realm)
  • The issuer from the payload to match the definition
  • The input data must pass the authFilter.

You need to plan for

  • the realms you need,
  • the mapping of subjects and realms to userids in the server,
  • public keys and keyrings

Some useful links

My Server’s definition

<featureManager> 
<feature>transportSecurity-1.0</feature>
<feature>openidConnectClient-1.0</feature>
</featureManager>

<openidConnectClient id="RSCOOKIE"

headerName="colin"

clientId="COLINCOO2"
realmName="zOSMF"
inboundPropagation="required"
issuerIdentifier="zOSMF"
mapIdentityToRegistryUser="false"
signatureAlgorithm="RS384"
jwkEndpointUrl="https://10.1.1.2:10443/jwt/ibm/api/zOSMFBuilder/jwk"
trustAliasName="CONN1.IZUDFLT"
trustStoreRef="defaultKeyStore"
userIdentifier="sub"
>
<authFilter id="afint">
<remoteAddress id="myAddress" ip="10.1.0.2" matchType="equals" />
</authFilter >

</openidConnectClient>

<keyStore
id="defaultKeyStore"
filebased="false"
location="safkeyring://IZUSVR/CCPKeyring.IZUDFLT"
password="password" readOnly="true" type="JCERACFKS"
/>

Where

  • headerName=”colin”. My curl request has -H “colin: $token” where token is the bash variable with the is the JWT token. Without this, the request needs -H “Authorization: Bearer $token”
  • realmName=”zOSMF” used with the userid to lookup in the RACMAP table for the z/OS userid to use
  • signatureAlgorithm=”RS384″ matches the value in the JWT. (I had problems using RS256)
  • trustAliasName=”CONN1.IZUDFLT” the name of the certificate to use on the server
  • trustStoreRef=”defaultKeyStore” points to the keyring definition

Why did my certificate mapping go wrong?

I had a working mapping for a Linux generated certificate to a z/OS userid. And then it wasn’t working. It took me 2 days before I had enlightenment. Although I had undone all of the changes I had made – well all but one.

I had defined

//IBMRACF  JOB 1,MSGCLASS=H 
//S1 EXEC PGM=IKJEFT01,REGION=0M
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
RACDCERT DELMAP(LABEL('colinpaice'))ID(IBMUSER)
RACDCERT MAP ID(IBMUSER) -
WITHLABEL('colinpaice') -
SDNFILTER('CN=colinpaice.O=cpwebuser.C=GB')
SETROPTS RACLIST(DIGTNMAP, DIGTCRIT) REFRESH
racdcert listMAP id(IBMUSER)
/*

Which says it the certificate with Subject: C = GB, O = cpwebuser, CN = colinpaice come in, then it maps to IBMUSER. Yes, the terms are in a different order, and there are “.” instead of “.” but it worked.

I started working with JSON Web Tokens (JWT), and it stopped working. The userid was coming out as IZUSVR – which is the userid of z/OSMF. I struggled with traces, and wrote my own little program to map the certificate to a userid – but still it was IZUSVR.

The enlightenment.

With JWT they are signed by a private key, and the public key is used to check the signature (that is check the checksum of the data is valid). To do this, the keyring needs the certificate in the keyring.
I was lazy and used the same certificate to sign the JWT, as I used to do certificate logon to z/OSMF.

To put the certificate in the keyring you need to import the certificate. I copied the certificate from Linux, using cut and paste and imported it

I used

//IBMRACF2 JOB 1,MSGCLASS=H 
//S1 EXEC PGM=IKJEFT01,REGION=0M
//SYSPRINT DD SYSOUT=*
//SYSTSPRT DD SYSOUT=*
//SYSTSIN DD *
RACDCERT CHECKCERT('COLIN.COLIN.PAICE.PEM')
RACDCERT DELETE (LABEL('COLINPAICE')) ID(IZUSVR)
RACDCERT ADD('COLIN.COLIN.PAICE.PEM') -
ID(IZUSVR) WITHLABEL('COLINPAICE') TRUST


RACDCERT ID(IZUSVR) CONNECT(RING(CCPKeyring.IZUDFLT) -
USAGE(CERTAUTH) -
LABEL('COLINPAICE') -
id(IZUSVR))

SETROPTS RACLIST(DIGTCERT,DIGTRING ) refresh
/*

This imports the certificate and associates it with the specified userid, ID(IZUSVR).
Now, when the certificate arrives as part of the certificate logon to z/OSMF, it checks to see if it is in the RACF data base – yes it is – under userid IZUSVR. It does not use the RACDCERT MAP option.

I reran this job with userid ADCDB – and the JWT had ADCDB in the definition.

To make it more complex, the Liberty Web Server within z/OSMF caches some information, and this complicated the diagnosis. In the evening it worked – next morning after IPL – it didn’t!

Lesson learned

Use one certificate for certificate logon, and another certificate for JWT.

What are RACF realms and how are they used.

A year or so ago I had come across the term realms in relation to security, but could not find what they are or how they are used.

I’ve been working with JSON Web Tokens, (to identify a Linux user to the MQWEB server on z/OS) and have found out what realms are.

The short answer is a realm is a zone of definitions it could be a machine, or a company. OK – this is not very clear.

The high level view

Take the scenario, I have a z/OS and a Linux environments. On both systems I have a userid COLIN.
When I create a JWT

  • on Linux I give it parameters subject:COLIN, issuer:LINUX
  • on z/OS I give it parameters subject:COLIN, issuer:zPROD

By some magic the JWT arrive at my web server, and I have configured the server to lookup the information in RACF.

In this scenario the realm is either LINUX or ZPROD.

I define mapping on z/OS (RACMAP) which say

  • For (subject: COLIN, Realm: LINUX) set the userid=NOONE
  • For (subject: COLIN, Realm: zPROD) set the userid=IBMUSER

So depending what is in the payload I can get a different userid on z/OS to issue my MQ commands.

A more complex example

In my MQWEB server I have definitions like

openidConnectClient: when issuerIdentifier=”zOSMF” and the signing certificate matches the keyring label “COLINPAICE” in keyring …. then use realm=”zPROD”

For user COLIN, this would match a RACMAP with

For (subject: COLIN, Realm: zPROD) set the userid=IBMUSER.

Whoops

With the configuration

<openidConnectClient
mapIdentityToRegistryUser="true"
...
/>

it ignored the realms, and used the sysplex name.

I got a RACF message

ICH408I USER(START1  ) GROUP(SYS1    ) NAME(####################) 
DISTRIBUTED IDENTITY IS NOT DEFINED:
ADCDC ADCDPL

My z/OS has sysplex name of ADCDPL.

Which says there was no mapping for the userid ADCDC, and the realm name ADCDPL. This took me half a day to resolve!

With this if I had configured RACMAP to have subject ADCDC and realm ADCDPL mapping to SYS1U – if a request came in from z/OS or Linux both would get userid SYS1U – which I am sure you do not want to happen.

The short answer is a realm is a zone of definitions it could be a machine, or a company. OK should now be perfectly obvious.

Wow, how to logon securely is so complex….

I’ve been looking into login on to Zowe and z/OSMF, and have realised how complex it is to set up secure logon.

An obvious fact about real life

If someone has access to your machine, for example a hacker, then they have access to your files, and data in virtual storage.

I cover

A lot of the time it is not difficult, but you need to handle the edge cases. When there are multiple dimensions to the problem, the edge case becomes the corner case, and this is where it gets really hard.

I use the expression is not secure. Being secure is relative. If your machine is air gapped with no external access, the machine should be secure. If people can access your files, perhaps on a shared machine, or a hacker can accessed your machine, I would not consider this secure.

The simplest case of logging on manually from a client machine to the server

Of course you use a TLS session to ensure the session traffic is encrypted.

You can type the values into the userid and password fields. It works, simple. If I disconnect and reconnect, I need to re-enter the userid and password

If I am using more than one back end server, I’ll have to enter the userid and password for each system, while the session is active.

Once the userid and password have been used, the fields in memory should be overwritten (but I doubt if this is always the case), to minimise the time window when a hacker extract the values from memory.

The simple case of logging on from a script from the client to the server.

You will not be there to enter the password so it needs to be stored.

The script may prompt you for the userid and password. You enter the information once, and it keeps them in virtual storage, and does not write them to disk. At the end of the session these values need to be overwritten in case a hacker is wandering round your system.

The application may write the information to disk – and we immediately hit a major problem. If a hacker has access to your system, then they can access (and copy away from your machine) the file where the password is stored. On Linux I displayed the contents of the “secure store” with a few lines of Python. I believe it is the same for Windows and Apple machines. (If your userid needs access to the secure store for something, a hacker thread running with your userid can access the store).

We have quickly seen that the use of a password stored on the machine is not secure.

Use of a certificate

As part of the TLS handshake there is a private key kept on the local machine, and a public key is sent to the partner.

Trusting a certificate

As part of setting up the public key, I send the public key to the server. The server’s Certificate Authority does a checksum of the public key, then encrypts the checksum with the server’s private key. The public certificate, encrypted checksum and the CA’s public key are sent back to the originator.

When the public certificate is used as part of the TLS handshake, the server does the same checksum calculation as before on public certificate, and saves it momentarily. It takes the encrypted checksum from the payload, decrypts it, and compares the two checksum values. If they match, then the client’s certificate can be trusted and has not been changed. Therefore the certificate is trusted (for a given level of trust) to represent the end user.

My program has access to my private key – so authenticates as me

As part of the handshake data, the client encrypts some data using its private key, and sends this to the server. The server uses the public key it received, and decrypts this value. If the decrypted data matches what it is expecting, then this is proof that the client has the private key and is who they say they are.

The server can then use the mapping of “name in the public key” to a userid, to determine which userid the requester should run under.

Once this is set up, the client can connect to the server without specifying a userid and password.

There is a proposal to reduce the validity time of personal certificates from over a year to 47 (or less) days. This reduces the time window when a certificate can be used.

This is great … but

If your private key is stored on your disk, a hacker can steal a copy of the file and impersonate you. So once again this is not very secure.

Use a private key – external to your computer

You can get a dongle, such as a Yubikey which keep the private key secured on a detachable USB dongle. To use it, a request is passed to the dongle saying “please encrypt this data”. It encrypts the data, returns it, and it can be sent to the server. You cannot extract the private key from the dongle.

To use this for authentication you physically need the dongle. If someone has control of your machine, they can use this dongle when it is plugged in. If they just have a copy of your files they cannot use the dongle, and so this is secure.

If you remove the dongle when you are not using it, we have a secure solution, until you plug it back in! Most people may forget and leave the dongle in all day.

I’ve been to sites, where the private key is stored on their badge. When they want to authenticate they put their badge on a badge reader and authenticate. This is awkward, so they immediately take their badge off the reader after authentication.

Setting up these external key stores is not trivial.

Use of Multi Factor Authentication(MFA)

One of the main approaches to MFA is

  • something you have
  • something you know

MFA is often used in applications such as online banking.For some transactions you will have a code sent to your email address. Something you know is your application password, something you have is the code sent to you. To use the service, an application password and access to your email is required.

MFA can also use one time passwords – so if someone is monitoring your network traffic they will not be able to reuse the one-time-password.

You can get the one-time-password generated from your badge, a dongle attached to your machine, or an application, such as an authenticator application on your phone. Once these apps have been configured to the back-end, you can press a button and get a one time code.

To logon you may need your normal logon password (which you change monthly) and the one time code from the MFA device. You might just need the one-time-code – depends on how the environment has been set up.

This is great for you logon to a backed and stay connected. If you have script, or are using multiple back-end servers. This becomes impractical. You need a new one-time-code each time you logon. This can be automated if the MFA device is attached to your machine, but not if you are using a mobile phone application.

One small problem is when the user’s password has expired. They are prompted to change it, and now need another One Time Password – and they may have to wait for a period (seconds) before a new one is generated.

JSON Web tokens

See Are JSON Web Tokens secure? – Yes if used properly.

A JSON Web Token(JWT) provides a time limited key, and so avoids the problems when changing a password. Some of the concepts are similar to a certificate logon, but with a limited validity, from minutes to hours.

With RACF these are known as Identity token. (Having a different name, means RACF development can extend the support to cover additional tokens types.)

A JWT has three parts

  • Information about the JWT. This JWT has used an RSA certificate, and algorithm SHA256.
  • Identity information. The userid is…, it was issued by ( z/OSMF, or SAF), at this time…. and is valid until….
  • The above parts are check summed, and the check sum is encrypted using a private key.

The above 3 parts are base 64 encoded, and joined together with a ‘.’ between them.

Once a JWT token is created, it is valid until it expires. You need to consider if you want a JWT to be valid for a few minutes, or all day.

You create (or have the system create for you) the header and the payload. You calculate the checksum, and encrypt the checksum with the private key.

The parts of the JWT are assembled and sent to the partner.

The partner uses its copy of the public key, and checks that the checksum matches what it expects and, if it matches, can use the information in the payload part. This validation could be a call to a RACF service, a https request to a server, or Java.

Once the JWT has been validated, the server can use the information in the payload.

You do not want to use the userid in the JWT directly, because the userid COLIN on one system has no authority, but has super user authority on another system.

With RACF you can map a userid string to a RACF userid using profiles defined with the RACMAP command. See the r_usermap service.

While the JWT is valid it could be used by a hacker.

Overall

It looks like there is not one good solution to cover all cases.

Someone said “assume your machine will be hacked. Minimise what damage they can do”. This may be as simple as turning your machine off overnight, so the hackers cannot control it

The solution for scripts may be different to people logging on.

You may want to have a userid just for scripts, which has only enough authority and permissions to work, and no additional permissions.

You need to consider the options and risks, and not just sleepwalk into having an insecure system.

What are JSON Web Tokens and how do they work?

Wikipedia says

JSON Web Token (JWT) is a proposed Internet standard for creating data with optional signature and/or optional encryption whose payload holds JSON that asserts some number of claims. The tokens are signed either using a private secret or a public/private key

What does a JWT look like?

A JWT has three parts

  1. Header: Information about the JWT itself, such as the Algorithm type.
  2. Payload: This contains information such as creation datetime, expiry datetime, secret key, userid.
  3. Encrypted checksum: (the signature) of parts 1 and 2.

A header looks like

{
"alg": "RS256"
}

A payload looks like

{
"sub": "IBMUSER",
"iat": 1753381341,
"exp": 1753410141,
"iss": "APIML",
"jti": "699cdd3b-832a-4b27-95f6-2a0a9fd5375a",
"dom": "security-domain"
}

Where

  • sub: is the principal (userid) within the payload
  • iat: issued time
  • exp: expiry time
  • jti: is unique value
  • dom: is the security domain.

These are converted to base64 where each 3 bytes are encoded as 4 printable characters.

The payload could include a userid and a file name, and because an authentication server has created the JWT, the server can send the file to the user without much more authentication.

How does it work?

I understand (from reading the limited online documentation), that it works as follows.

Create your payload.

Some fields you might specify are

  • sub: subject – such as a userid
  • iat: The issued time
  • exp: The expiry time
  • jti: A unique value for the JWT
  • You might put in a public certificate name
  • you can include a public key as a JSON Web Key (JWK) representation of cryptographic key.

This will include the algorithm used to sign the JWT.

Calculate the signature

You use a private key, and the algorithm, and calculate the signature (encrypted checksum) of the header and payload, and append it to the header and payload.

How long should it be valid for?

The JWT has the creation time, and the expiry time, after which the JWT is no longer valid.

You need to consider what valid interval to use. Remember a JWT is a pre-authorised userid token. It can be used multiple times before it expires. A hacker could copy it and use it.
You might chose a short time if it is just used to establish a session, and is not used again.

You might use a longer interval if you keep connecting and disconnecting from servers. Once it expires you have to request another JWT. The longer the expiry interval, the longer a hacker can use the JWT.

Creating the JWT

There are different ways of creating the JWT. For example

  • You can use Java
  • use the Python package pyJWT
  • Use RACF services. This creates a JWT. It requires supervisor state, so is not easy to set up. You can create RACF profiles which are used to create the JWT.

Send the JWT to the requester

The JWT is sent to the client which can use it to authenticate with a back end server which supports JWTs.

The server authenticates the JWT

This is server dependent.

  • One approach is to go through every public certificate in the key store, until the right one is found
  • You could specify the name of the public certificate in the header, and use that.

Note. This is different to TLS and certificates. With TLS the public certificate is sent as part of the handshake. The public key is authenticated by the Certificate Authority (CA) at the recipient. With TLS you only needs the CA certificate, not the individual certificates to validate. (Except for self signed which you should not be using). With a JWT you need the public certificate, it is not passed within the JWT

Note 2. There is discussion about a server sending the jti value in the JWT to an authorisation server, to get back the public key, which can be validated with a CA etc.

The back end may need to validate with the authorisation server. The request may be along the lines of “I’ve received userid COLIN, and the JTI from the JWT value is COLIN123456… is this valid?” This could be a cross memory request, for example using a RACF service, or it could be a TLS request to a server.

If the response is positive, then you can trust the information in the payload.

Use the information in the JWT payload.

The backend should not just use the userid in the payload, because I could easily create my own JWT (such as with pyJWT), as long as the server has my public key, the signature matches and verifies my unofficial JWT, and so I get unauthorised access.

With the openidConnectClient support in Liberty ( used by z/OSMF, MQWEB, and Zowe) you can have multiple openidConnectClient definitions. Each definition can have a filter, so you can restrict each openidConnectClient definition to a particular IP address range, or URL, (or both).

With openidConnectClient you specify the realm name to be used.

On midrange machines, it looks like the userid may be taken from the payload, and used!

Mapping name information to RACF userid.

Once the JWT has been validated in the openidConnectClient definition, the code looks up the JWT userid and realm combination to find which z/OS userid should be used.

There is a RACF service R_usermap (IRRSIM00): Map application user which maps a name and realm to a RACF userid.

You can define the mapping using the RACF command RACMAP, for example:

RACMAP ID(COLIN) MAP USERDIDFILTER(NAME('COLIN')) - 
REGISTRY(NAME('PRODZOSMF')) -
WITHLABEL('COLIN')

RACMAP ID(ADCDA) MAP USERDIDFILTER(NAME('CN=HACKER')) -
REGISTRY(NAME('*')) -
WITHLABEL('ADCDA')


RACMAP ID(COLIN) LISTMAP
RACMAP ID(ADCDA) LISTMAP
SETROPTS RACLIST(IDIDMAP) REFRESH

This says

  • if userid COLIN from the JWT, and the realm PRODZOSMF, then use userid COLIN.
  • If userid HACKER from the JWT in any realm, then use userid ADCDA.

If you get RACF abend 648 reason 0000004 see here.

To use this the userid using r_usermap needs permission.

PERMIT IRR.IDIDMAP.QUERY CL(FACILITY) ID (IBMUSER) ACCESS(READ) 
SETROPTS RACLIST(FACILITY) REFRESH

Note. The input to r_usermap is UTF8 (ascii) so the COLIN was passed in as 0x434F4C494E.

Use of different registries.

Because there may be multiple sites sending a JWT, you can use the REGISTRY name to further qualify the mapping. The registry is passed (in ASCII) on the r_usermap call – but you can specify a generic * (ASCII 0x2a) for any registry.

In the JWT above it had “dom”: “security-domain”. You could specify this as the registry.

Planning is required

To use the r_usermap service you need to plan before you start.

  • What format “userid” strings will be used? Is this just a string, or you you create a compound string “origin”.”userid” where origin and userid are taken from the JWT
  • Use of the registry field. If you want to use the registry field to identify different originators machines, you need to ensure that the JWTs are produced with the appropriate domain. If all requests have “dom”:”security-domain” it adds no value.