Tracing encrypted data to z/OS

I had blogged Collecting a wire-shark trace with TLS active for a browser where you could specify an environment variable export SSLKEYLOGFILE=$HOME/sslkeylog.log. OpenSSL would write the key to this file, and Wireshark could decrypt the traffic using this data.

Unfortunately this only worked with RSA keys. I could not get it to work with modern Elliptic Curve keys.

I’ve updated my zWireshark program to capture AT-TLS application data in clear text from the z/OS side. It uses an IBM provided API, and captures the traffic between AT-TLS and the application.

You need to set up security profiles, for example

permit  EZB.TRCSEC.*.*.ATTLS            - 
CL(SERVAUTH) id(ADCDB) access(READ)
permit EZB.TRCCTL.S0W1.TCPIP.DATTRACE -
CL(SERVAUTH) id(ADCDB) access(READ)
permit EZB.TRCCTL.S0W1.TCPIP.OPEN -
CL(SERVAUTH) id(ADCDB) access(READ)
permit EZB.TRCCTL.*.*.* CL(SERVAUTH) id(ADCDB) access(READ)
SETROPTS RACLIST(SERVAUTH) refresh

and change the AT-TLS configuration to include CtraceClearText On

For my web browser traffic it produced (printed with the ASCII switch)

Data trace.  Data length 345.  ATTLS Clear Text.                             
<GPMSERVE 11:01:57.258946 Src 10.1.1.2 Port 8803 Dst 10.1.0.2 Port 45168
Warning: 199 RMF-DDS-Server SeverityCode(03) Data(0)
Content-Location: perform_20250102110157_20250102110157.xml
Cache-Control: max-age=30
Date: Thu, 02 Jan 2025 11:01:57 GMT
Connection: close
Content-Length: 1468
Content-Type: application/xml
X-UA-Compatible: IE=edge
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block

When the AT-TLS option CtraceClearText was Off, the output was

Data trace.  Data length 0.  PTH_Flag.Confidential.                           
<GPMSERVE 11:27:48.733616 Src 10.1.1.2 Port 8803 Dst 10.1.0.2 Port 52860

So no confidential data was displayed.

The JCL is

//COLINC5    JOB 1,MSGCLASS=H,COND=(4,LE) 
// SET LOADLIB=COLIN.ZWIRESHA.LOAD
//RUN EXEC PGM=TCPDATA,REGION=0M,PARMDD=MYPARMS
//STEPLIB DD DISP=SHR,DSN=&LOADLIB
//SYSPRINT DD SYSOUT=*,DCB=(LRECL=200)
//SYSOUT DD SYSOUT=*
//SYSERR DD SYSOUT=*
//* MYPARMS needs a / to split LE parms from program parms
//* needs a blank before each parm, because trailing blanks removed
//MYPARMS DD *
/
--IP 10.1.0.2
--WAIT 60
--DEBUG 0
--DISCARD 1
--PRINT A
/*

Please let me know of any problems or suggestions

Collecting and understanding a RACF GTF trace output

A RACF request can be

  • A callable service, such as IRRSDL00 which is used to extract information about keyrings. This can be used by High Level Languages. These have a trace type of OMVS
  • A RACROUTE request, an assembler macro interface, such as FASTAUTH. These have a trace type of RACF.

You get a trace entry PRE the call, and and a trace entry POST the call. A callable service would have an OMVSPRE, and an OMVSPOST entry.

When setting up your RACF trace you need to define which callable service, or which RACROUTE requests you want to trace.

Example of a callable server

Entry trace

Trace Identifier:             00000036 
Record Eyecatcher: RTRACE
Trace Type: OMVSPRE
Ending Sequence: ........
Calling address: 00000000 20801A0F
Requestor/Subsystem: ........ ........
Primary jobname: IBMINC5
Primary asid: 00000029
Primary ACEEP: 00000000 008FA8A8
Home jobname: IBMINC5
Home asid: 00000029
Home ACEEP: 00000000 008FA8A8
Task address: 00000000 008D6A88
Task ACEEP: 00000000 00000000
Time: DFF36A27 5460CC00
Error class: ........
Service number: 00000029
RACF Return code: 00000000
RACF Reason code: 01000000
Return area address: 00000000 00000000
Parameter count: 00000021

Interesting information

  • Trace Type: OMVSPRE this is an entry trace
  • Primary jobname: IBMINC5
  • Home jobname: IBMINC5
  • The RACF return and reason code have no meaning on entry
  • Service number: 00000029 is hex so the service is 41. The RACF commands documentation says for a callable service, 41 is function number IRRSDL00.

Exit trace

Trace Identifier:             00000036 
Record Eyecatcher: RTRACE
Trace Type: OMVSPOST
Ending Sequence: ........
Calling address: 00000000 20801A0F
Requestor/Subsystem: ........ ........
Primary jobname: IBMINC5
Primary asid: 00000029
Primary ACEEP: 00000000 008FA8A8
Home jobname: IBMINC5
Home asid: 00000029
Home ACEEP: 00000000 008FA8A8
Task address: 00000000 008D6A88
Task ACEEP: 00000000 00000000
Time: DFF36A27 5468DC40
Error class: ........
Service number: 00000029
RACF Return code: 00000008
RACF Reason code: 00000014
Return area address: 00000000 00000000
Parameter count: 00000021

Interesting fields

  • Trace Type: OMVSPOST this is the exit trace
  • As above service number 41 is function
  • The RACF Return code is 8
  • The RACF reason code is 14

Example of a RACROUTE trace

Entry

Trace Identifier:             00000036 
Record Eyecatcher: RTRACE
Trace Type: RACFPRE
Ending Sequence: ........
Calling address: 00000000 A096A08A
Requestor/Subsystem: CRYPTO CRYPTO
Primary jobname: CSF
Primary asid: 00000038
Primary ACEEP: 00000000 008F6B98
Home jobname: IBMEXP
Home asid: 00000029
Home ACEEP: 00000000 008FA8A8
Task address: 00000000 008D6A88
Task ACEEP: 00000000 00000000
Time: DFF37DD1 AB7FF040
Error class: ........
Service number: 00000002
RACF Return code: 00000000
RACF Reason code: 00000000
Return area address: 00000000 00000000
Parameter count: 00000009

This trace entry was from a batch job IBMEXP, using an ICSF service to access an encryption key.

Interesting fields

  • RACFPRE this is a RACROUTE before entry
  • Requestor/Subsystem: CRYPTO which z/OS component
  • Primary jobname: CSF This was the address space which issued the RACROUTE request
  • Home jobname: IBMEXP This was the job requesting an ICSF service
  • Service number: 2. The RACF commands documentation says for a RACROUTE service, 2 is FASTAUTH
  • Ignore RACF return and RACF reason codes. They are set on exit,

Exit

Trace Identifier:             00000036 
Record Eyecatcher: RTRACE
Trace Type: RACFPOST
Ending Sequence: ........
Calling address: 00000000 A096A08A
Requestor/Subsystem: CRYPTO CRYPTO
Primary jobname: CSF
Primary asid: 00000038
Primary ACEEP: 00000000 008F6B98
Home jobname: IBMEXP
Home asid: 00000029
Home ACEEP: 00000000 008FA8A8
Task address: 00000000 008D6A88
Task ACEEP: 00000000 00000000
Time: DFF37DD1 AB85DE40
Error class: ........
Service number: 00000002
RACF Return code: 00000008
RACF Reason code: 00000000
Return area address: 00000000 00000000
Parameter count: 00000009

This trace entry was from a batch job IBMEXP, using an ICSF service to access an encryption key.

Interesting fields

  • RACFPOST this is a RACROUTE after exit entry
  • Requestor/Subsystem: CRYPTO which z/OS component
  • Primary jobname: CSF This was the address space which issued the RACROUTE request
  • Home jobname: IBMEXP This was the job requesting an ICSF service
  • Service number: 2. The RACF commands documentation says for a RACFROUTE service, 2 is FASTAUTH
  • RACF Return code: 00000008, RACF Reason code: 00000000 This shows there was a problem. The documentation for FASTAUTH shows
    • RC=8 -> the user or group is not authorized to use the resource.
    • RS=0 -> The invoker does not need to log the request.

The RACF trace command for this was

#SET TRACE(JOBNAME(csf),callable(none),RACROUTE(type(2)))

and the following commands

S GTF.GTF
R 1,trace=usrp
R 2,USR=(F44)
R 3,END
R 4,U

Fast start GTF

I can use

S GTF.GTF,M=GTFRACF

Where my GTF proc has

//GTFNEW  PROC M=GTFPARM,ID=SYS1 
//DELETE EXEC PGM=IEFBR14
//IEFRDER DD DSNAME=&ID..TRACE,UNIT=SYSDA,SPACE=(TRK,20),
// DISP=(MOD,DELETE)
//IEFPROC EXEC PGM=AHLGTF,PARM='MODE=EXT,DEBUG=NO,TIME=YES,NP',
// TIME=1440,REGION=2880K
//IEFRDER DD DSNAME=&ID..TRACE,UNIT=SYSDA,SPACE=(TRK,20),
// DISP=(NEW,KEEP)
//SYSLIB DD DSNAME=USER.Z24C.PROCLIB(&M),DISP=SHR

I have a member in USER.Z24C.PROCLIB(GTFRACF)

TRACE=USRP 
USR=(F44)
END

I want to be someone else – or using pthread_security_np

There are times when you are running a job, and you want to do some work as a different userid.

You can configure a userid so it acts as a surrogate – I can submit jobs with your userid, without knowing your password.

You can have a server and have a thread within the server run as different userid by specifying a userid and password, using the pthread_security_applid_np function. You can also have it logon using a certificate, and it looks up the userid from the certificate.

You can extend/restrict this by specifying an RACF APPL which the user must have access to.

RDEFINE APPL YYYY 
PERMIT  CLASS(APPL) YYYY ID(COLIN) ACCESS(READ) 
SETROPTS RACLIST(appl ) REFRESH

My job running with userid IBMUSER specifies a userid COLIN, and COLIN’s password. If applid YYYY is specified, userid COLIN must have read access to the YYYY in CLASS(APPL).

If you use pthread_security_np() is the same as pthread_security_applid_np() and uses the default OMVSAPPL.

My example C program is

Main

int main( int argc, char *argv[]) 
{ 
  pthread_t thid; 
  int rc; 
  void *ret; 
  if (pthread_create(&thid, NULL, thread, "thread 1") != 0) { 
    perror("pthread_create() error"); 
    exit(1); 
  } 
  rc =pthread_join(thid, &ret); 
  printf("Pthread join %d\n",rc); 
  if (rc  != 0) { 
 // perror("pthread_create() error"); 
 // return(3); 
  } 
  printf("thread exited with '%s'\n", ret); 
  return 0  ; 
} 

  #pragma runopts(POSIX(ON)) 
  /*Include standard libraries */ 
  #include <stdio.h> 
  #include <stdlib.h> 
  #include <string.h> 
  #include <stdarg.h> 
  #include <errno.h> 
  #define _OPEN_SYS 1 
  #include <pthread.h> 
                                                                    
  void *thread(void *arg) { 
  char *ret = ""; 
  int rc; 
  printf("thread() entered with argument '%s'\n", arg); 
  rc = pthread_security_applid_np(__CREATE_SECURITY_ENV, 
              __USERID_IDENTITY, 
              5, 
              "COLIN", 
              "PASSW0RD", 
              0,"AAAA"); 
  perror("perror"); 
  printf("colin rc = %d errno %d\n",rc,errno); 
  if (rc == 0) 
  { 
     FILE * f2  ; 
     f2 = fopen("DD:TEST","r                      ") ; 
     printf("DD:TEST  %d\n",f2); 
   } 
  pthread_exit(ret); 
  } 



int main( int argc, char *argv[]) 
{ 
  pthread_t thid; 
  int rc; 
  void *ret; 
  if (pthread_create(&thid, NULL, thread, "thread 1") != 0) { 
    perror("pthread_create() error"); 
    exit(1); 
  } 
  rc =pthread_join(thid, &ret); 
  printf("Pthread join %d\n",rc); 
  if (rc  != 0) { 
    perror("pthread_create() error"); 
   return(3); 
  } 
  printf("thread exited with '%s'\n", ret); 
  return 0  ; 
} 

When the above program ran it gave me

ICH408I USER(COLIN   ) GROUP(SYS1    ) NAME(COLIN PAICE         ) 
  IBMUSER.TRY.PEM CL(DATASET ) VOL(C4USR1)                        
  INSUFFICIENT ACCESS AUTHORITY                                   
  ACCESS INTENT(READ   )  ACCESS ALLOWED(NONE   )                 

which shows that the userid COLIN was used.

EDC5167I Access to the UNIX System Services version of the C RTL is denied. (errno2=0xC10F0001 C10F0001) .

You cannot use the pthread_security… functions in the main thread. You have to attach a subtask using pthread_create().

EDC5143I No such process. (errno2=0x0BE80000 0BE80000)

Invalid userid specified.

EDC5111I Permission denied. (errno2=0x0BE80000 0BE80000)
Invalid password.

EDC5163I SAF/RACF extract error. (errno2=0x0BE8081C 0BE8081C)

Revoked userid.

EDC5168I Password has expired. (errno2=0x0BE80000 0BE80000)

Obvious.

EDC5163I SAF/RACF extract error. (errno2=0x0BE80820 0BE80820)

No OMVS segment, or the new user does not have access to the specified applid class.

rc = pthread_security_np() defaults to pthread_security_applid_np() with an applid of OMVSAPPL

ICH70004I 
ATTEMPTED 'READ' ACCESS OF USER(ADCDE) GROUP(TEST) NAME(ADCDE) ENTITY 'OMVSAPPL' IN CLASS 'APPL' 

EDC5139I Operation not permitted. (errno2=0x0BE802AF 0BE802AF)

ICH420I PROGRAM CERT FROM LIBRARY COLIN.LOAD CAUSED THE ENVIRONMENT TO
BECOME UNCONTROLLED.
BPXP014I ENVIRONMENT MUST BE CONTROLLED FOR SERVER (BPX.SERVER)
PROCESSING.

Needed

RALTER PROGRAM CERT ADDMEM('COLIN.LOAD'//NOPADCHK)
SETROPTS WHEN(PROGRAM) REFRESH

Easy AT-TLS configuration and reporting

This blog post discusses AT-TLS configuration, and my EasyAT-TLS code on GitHub to make it easy to configure AT-TLS and give an compact and useful configuration report.

A lot of documentation is written by experts for experts, from the developer’s viewpoint; rather than by experts for people who just want to get the job done. It feels that AT-TLS exposes to much of the structure of how data is held internally. I found it was very easy to get lost, and configuring it is hard. z/OSMF has a “configure AT-TLS” workflow – but it just creates a workflow of the steps. It did not make it easier to configure AT-TLS.

As a general philosophy, rather than the traditional approach of “here are all the keywords in alphabetical order”, I would provide information in different topics.

  • Keywords that everyone uses, and what you will need to get started,
  • More advanced keywords that most people might use,
  • Even more advanced keywords that only experts would use.

and then list the keywords alphabetically within topic. For an inexperienced user this makes it very clear what options they need to specify to get started. If you have a configuration tool (or online documentation) have a button which allows you to specify what your experience level is, and display or configure the information at the appropriate level.

The problem

What does an AT-TLS configuration file look like?

Part of the definition for one rule is

TTLSRule                      COLATTLJ 
{
LocalPortRange 4000
Jobname COLCOMPI
Userid COLIN
Direction BOTH
RemoteAddr 10.1.2.2/32
TTLSGroupActionRef TNGA
TTLSEnvironmentActionRef TNEA
TTLSConnectionActionRef TNCA
}
TTLSGroupAction TNGA
{
TTLSEnabled ON
}
TTLSEnvironmentAction TNEA
{
HandshakeRole ServerWithClientAuth
TTLSKeyringParms
{
Keyring start1/TN3270
}
TTLSSignatureParmsRef TNESigParms
}
TTLSSignatureParms TNESigParms
{
CLientECurves Any
SignaturePairs 060305030403

}......

There is a lot of structure, with values beginning with TTLS.

Without the structure the data looks like

policyRule : COLATTLJ
LocalAddr : All
RemoteAddr : '10.1.1.2/32'
LocalPortRange : 4000-4000
JobName : COLCOMPI
UserId : COLIN
Direction : Both
TTLSEnabled : On
Trace : 255
HandshakeRole : ServerWithClientAuth
Keyring : start1/TN3270
TLSv1.1 : Off
TLSv1.2 : On
TLSv1.3 : Off
HandshakeTimeout : 3
ClientECurves : Any
ServerCertificateLabel : NISTECCTEST
V3CipherSuites : [
003D TLS_RSA_WITH_AES_256_CBC_SHA256,
C02C TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
]

Which I find much clearer.

Once you have configured AT-TLS, you can use the Unix command pasearch to report the configuration. This gives output like

policyRule:             AZFClientRule                                  
Rule Type: TTLS
...
Time Periods:
Day of Month Mask:
First to Last: 1111111111111111111111111111111
Last to First: 1111111111111111111111111111111
Month of Yr Mask: 111111111111
Day of Week Mask: 1111111 (Sunday - Saturday)
Start Date Time: None
End Date Time: None
...
TTLS Condition Summary: NegativeIndicator: Off
Local Address:
FromAddr: All
ToAddr: All
Remote Address:
FromAddr: 0.0.26.137
ToAddr: 0.0.26.137
LocalPortFrom: 0 LocalPortTo: 0
RemotePortFrom: 0 RemotePortTo: 0
JobName: AZF* UserId:
ServiceDirection: Outbound
...

The output for one rule is over 180 lines long, and gives the configuration, including any defaults; not what is actually used. (Some fields can be configured in more than one place, so you need to know which is actually used). Compare this with my tool’s output above.

Easy AT-TLS on GitHub

Displaying a concise view of the data.

On GitHub is code which removes all of the “structure” from the pasearch report to leave just the non default data. You get, for one rule

policyRule : COLATTLJ
LocalAddr : All
RemoteAddr : '10.1.1.2/32'
LocalPortRange : 4000-4000
JobName : COLCOMPI
UserId : COLIN
Direction : Both
TTLSEnabled : On
Trace : 255
HandshakeRole : ServerWithClientAuth
Keyring : start1/TN3270
TLSv1.1 : Off
TLSv1.2 : On
TLSv1.3 : Off
HandshakeTimeout : 3
ClientECurves : Any
ServerCertificateLabel : NISTECCTEST
V3CipherSuites : [
003D TLS_RSA_WITH_AES_256_CBC_SHA256,
C02C TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
]

formatted as a YAML file, which I find much easier to use!

The output of the pasearch file was 2296 lines, the output file from my Python program was 184 lines!

Generating AT-TLS definitions

I found it hard to generate AT-TLS definitions because you need to know the AT-TLS structure; or have an existing entry to copy. For example a keyring is defined in a TTLSKeyringParms definition in TTLSEnvironmentAction statement which is pointed to by a high level TTLSRule. See the partial example above. You should not need to know this information.

As an end user I just want to define a keyring, and let the computer put it into the “internal format”.

With AT-TLS, you can group common information into a unit, and refer to that. I found I got lost trying to work out what I was using, and what else used it. Making one small change is complex as you had to do lots of copying to generate new groups, and then you have these groups lying around which may not be used again.

Generating AT-TLS definitions the easy way

The code to extract the key information from a pasearch output produces a YAML file. The genATTLS code takes a YAML file and generates the AT-TLS input file with all of the structure that AT-TLS requires. The output file can then be used by the Policy Agent on z/OS.

I’ve written the code from an end user perspective. For example I have a YAML file which defines two rules.

---
policyRule : Rule1
Priority : 255
LocalAddr : All
RemoteAddr : All
LocalPortRange : 6794-6794
Direction : Inbound
TTLSEnabled : On
Trace : 255
HandshakeRole : ServerWithClientAuth
Keyring : start1/TN3270
TLSv1.1 : Off
TLSv1.2 : On
TLSv1.3 : Off
HandshakeTimeout : 120
ServerCertificateLabel : RSA2048
---
policyRule : Rule2
Priority : 255
LocalAddr : All
RemoteAddr : All
LocalPortRange : 6793-6793
Direction : Inbound
TTLSEnabled : On
Trace : 255
HandshakeRole : Server
Keyring : start1/TN3270
TLSv1.1 : Off
TLSv1.2 : On
TLSv1.3 : Off
CertificateLabel : RSA2048
ServerCertificateLabel : RSA2048

This can be simplified by using “BasedOn” to refer to other sections, such as the “common” rule

---
# This first section is common to the others
policyRule : common
TLSv1.1 : Off
TLSv1.2 : On
TLSv1.3 : Off
Keyring : start1/TN3270
Priority : 255
LocalAddr : All
RemoteAddr : All
Direction : Inbound
TTLSEnabled : On
Trace : 255
CertificateLabel : RSA2048
ServerCertificateLabel : RSA2048
---
policyRule : Rule1
BasedOn : common
LocalPortRange : 6794-6794
HandshakeRole : ServerWithClientAuth
HandshakeTimeout : 120
---
policyRule : Rule2
BasedOn : common
LocalPortRange : 6793-6793
HandshakeRole : Server
---

The Python script generates the definitions from this file, 110 lines of output from a 28 line input file!

It is easy to extend. You just specify the overrides

policyRule : Rule2A
BasedOn : Rule2
TLSv1.3 : On
Priority: 300

The priority:300 says this definition should be used over Rule2, because Rule2 only has priority 255.

Testing multi system ICSF on a single system.

I only have a single z/OS image, and as part of testing ICSF in a multi system environment, I needed a second ICSF environment. I am the only user on my system, so I can stop and start ICSF as I please.

I set up a second set of ICSF datasets, and a second CSPRMxx parmlib member. I then used

P CSF
S CSF,PRM=C2
D ICSF,KDS

The D ICSF,KDS command, tells you what ICSF data sets are being used.

When using CSFKGUP I was careful to specify the correct dataset

//STEP10 EXEC PGM=CSFKGUP
// SET CKDS=COLIN.SCSFCKDS.NEW

To go back to my primary system.

P CSF
S CSF

What ICSF APIs do I need to use for AES CIPHER keys

I’ve done a lot of work using ICSF to generate keys programmatically for data set encryption, and I found it hard to remember what the different APIs are,and what options to specify. This blog post covers the main APIs.

AES keys are better than DES keys, because they are more secure (take longer to break) and use less CPU.

There are different key tokens formats within ICSF. AES uses a variable key format. Within AES keys you can have types

  • CIPHER used for encrypting data – such as data set encryption
  • EXPORTER and IMPORTER, these are used to encrypt keys for sending to remote systems. They are known as Key Encrypting Keys.
  • DATA – CIPHER keys are better than DATA because they have more granular control. You can say a CIPHER key can be used to encrypt an AES key – but not a DES key. With DATA it is all or nothing.
  • Others – I haven’t played with these

For many APIs you can give the name of a token in the CKDS data set. The names are 64 characters long. You can also use a copy of a token in memory, for example your program reads a token from the CKDS, or you have been sent a token from a remote systems. These will have a length typically shorter than 64 characters. ICSF uses the length you specified to determine what information is being passed in.

Generating a key token

You can use CSNBKGN2 (Key Generate2) to generate a key token.

You can specify type CIPHER. This generates a key token using some defaults. The defaults are not suitable for generating keys for data set encryption.

You can specify type TOKEN. You pass a skeleton token key with some of the options specified. You could generate this yourself, setting all of the bits you need, but it is easier to use the Key Token Build2 (CSNBKTB2) API to generate the key token. For example you specify rule options as character strings such as “CIPHER”,”XPRTCPAC”,”ANY-MODE”, which are needed for data set encryption.

You can specify the hex string to be used as the key value, by using the skeleton. You specify clear_key_bit_length and the clear_key_value. If clear_key_bit_length is zero in the skeleton, no key value is used and the system generates a random value. You will need to specify a valid value in the Key Generate2 field clear_key_bit_length. If you want to specify the hex value, set the two clear_key_bit_length fields to the same value in CSNBKTB2 and CSNBKGN2.

You can create a key token for use

  • on the current system. Once you have successfully generated your key token, you can use it to encrypt data, but you are more likely to store it in the local PKDS service using the PKDS Key Record Create (CSNDKRC) API. You might need to use the Key Record Delete (CSNDKRD) API if the key already exits in the PKDS. There is no write with replace service.
    • At a later date you can use the ICSF API to extract the key and encrypt with an EXPORTER or IMPORTER key to send to a remote site
  • and/or you can get a copy encrypted with a Key Exporting Key (EXPORTER or IMPORTER) so it can be securely sent to another site. You can
    • export it using an EXPORTER Key Encrypting Key to send to a remote site.
    • export it using an IMPORTER key, so it can be imported into the local system at a later date. I do not think this is a common scenario.

List the CKDS

You can search the CKDS passing various criteria using function Key Data Set List (CSFKDSL). It returns the label of each record matching the criteria. For each record you can use Key Data Set Metadata Read (CSFKDMR) which displays information such as create time, and if it is archived or not. You can read the record from the CKDS using CKDS Key Record Read2 (CSNBKRR2). The actual key value is encrypted, but there are many fields which are available, such as this is an AES CIPHER key.

You can get the size of the key value within the token using the Key Test2 (CSNBKYT2) API, with the KEY-LEN option.

Get the checksum of the token

There is an API to get the checksum of a token. (I do not think this checksum is stored in the token, as you can specify different algorithms.) If you send the token to a remote system, you can check the checksum is the same as the original, and that the token has not been changed. The Key Test2 (CSNBKYT2) API, with the GENERATE option, returns the checksum value. You can specify which algorithm to use, for example SHA-256 or ENC-ZERO. There is a VERIFY option which seems to compare the value you pass with the calculated value, and if they do not match gives reason code 9000.

Reading a key from the CKDS

You can use the CKDS Key Record Read2 (CSNBKRR2) API to read a record from the CKDS into memory. There is an API CKDS Key Record Read (CSNBKRC2) which reads old format records from the CKDS.

Using ICSF API function CSNBKGN2 to generate keys

The ICSF function CSNBKGN2 allows you to generate keys for encrypting data (such as data sets) where you can

  • specify the hex data for the key value
  • have ICSF generate the key value securely so that the key value is never exposed as clear text.

Once a key has been created the key value will either be encrypted with the hardware key of the cryptographic hardware the program is running on, or is encrypted with Key Encryption Key, so it can be securely sent to a remote system.

You can generate a key in two modes:

  1. For use on the local system. This is known as the OP mode, because the cipher key is OPerational immediately. You can use it immediately to encrypt data, or you can use another ICSF function to write the key data to the CKDS. Once it has been written to the CKDS, you can refer to and use it by its label. At a later date you can extract the key and encrypt with another key for sending to a remote system.
  2. Encrypted under a transport key, called a Key Encrypting Key (KEK) an EXporter or IMporter key). You can write the created key to a file, send the file to a remote system, and then use an ICSF function(CSNDSYI2) to (decrypt it and re-encrypt it with the hardware key) and then write it to that systems CKDS. This is mode EX or IM.

The documentation for CSNBKGN2 has information about all of the options, but it is not very clear on how to use it!

I found it easier to understand the concepts, and to use, if I used two steps; generate a cipher key and then export it from the CKDS using a KEK, rather than trying to generate a cipher key for local use and encrypt it with a KEK in a single API call (point 3. below).

The CSNBKGN2 function has several modes:

  1. Generate a cipher key which you can use to encrypt data. This is known as the OP mode, because the cipher key is OPerational immediately.
  2. Generate a cipher key which is encrypted with a Key Encrypting Key. This is mode EX or IM.
  3. Generate a key you can use to encrypt data – both in OP mode, and generate the same key encrypted with a KEK, so you can send it somewhere. This is a combination of 1. and 2. in one API request (You specify mode = OPEX or mode = OPIM).
  4. Generate an exporter/importer pair of keys for encrypting other keys.
    • One of the pair is (usually) created so it can be used immediately, or written to the CKDS. This is known as the OP mode.
    • The other is encrypted with an existing Key Exporting Key. This is mode EX or IM.
    • The combined mode is OPEX or OPIM.

CSNBKGN2 provides some defaults which are suitable for some keys, but not suitable for AES CIPHER keys used to encrypt data sets.

You can create a skeleton key using CSNBKTB2, to specify non default parameters, and get CSNBKGN2 to use the skeleton. If you want to restrict what a key can do, you will need to use the skeleton. For example you can say this exporter/importer key pair can be used to encrypt AES keys, but not DES keys. For more information see the documentation. You tell CSNBKGN2 to use a skeleton by using the TOKEN keyword instead of CIPHER, EXPORTER, or IMPORTER.

Guidance on using CSNBKGN2

Lengths of data

When using some values such as KEKs, you can specify a key name in the CKDS of length 64 bytes. You can also read the key token from CKDS yourself (or get the key token from somewhere else). In this case the length you specify of the KEK may be ignored and the length of the data in the key token is used.

If an ICSF function provides you a key token, it will check your buffer is big enough, and return the length it used.

The function complains if you give a length field more than the maximum value. So when the documentation says generated_key_identifier_2_length is the maximum value is
900 bytes.
You must provide an area of the specified size, because it will complain if a different size is used.

Terms

The documentation uses the term generator key identifier. I think of this as the skeleton input.

My basic use of CSNBKGN2

Because of many parameters, I’ll give an example of my “standard” usage. The documentation gives the syntax

CALL CSNBKGN2(
return_code,
reason_code,
exit_data_length,
exit_data,
rule_array_count,
rule_array,
clear_key_bit_length,
key_type_1,
key_type_2,
key_name_1_length,
key_name_1,
key_name_2_length,
key_name_2,
user_associated_data_1_length,
user_associated_data_1,
user_associated_data_2_length,
user_associated_data_2,
key_encrypting_key_identifier_1_length,
key_encrypting_key_identifier_1,
key_encrypting_key_identifier_2_length,
key_encrypting_key_identifier_2,
generated_key_identifier_1_length,
generated_key_identifier_1,
generated_key_identifier_2_length,
generated_key_identifier_2 )f

Some constants I frequently use.

int rule_count = 2;
char * pKeyType1 = "EXPORTER";
char * pKeyType2 = "IMPORTER";
int lKey1 = 64;
int lKey2 = 64;
char KeyName1[64] ...
char KeyName2[64] ...
int lKekName = 64;
char KekName[64] ....
int lData1 = 900;
char data1[lData1];
char * pData1 = &data1[0];
int lData2 = 900;
char data2[lData2];
char * pData2 = &data2[0];

I used the parameters to CSNBKGN2 to define an Exporter/Importer

  • Return code &rc,
  • Reason code &rs,
  • exit data length 0
  • exit data array 0. This is not used because the previous field gives the length as 0.
  • &rule_count, this had the value 2 because pRule below pointed to two parameters.
  • pRule this pointed to {AES, OPEX}. AES is the type of cipher; OPEX says generate the first one so it can be written to the local CKDS, EX says use the second Key Exporting Key – which has a type of EXPORTER.
  • &keyBitLength keyBitLength has the value 256. This specifies how secure to make the cipher.
  • pKeyType1 where KeyType1 was EXPORTER.
  • pKeyType2 where KeyType2 was IMPORTER.
  • &lKey1 value 64. This is the length of the string following – the value of the key name stored in the first key. Set this to zero if you do not want a name stored in the first generated key.
  • &KeyName1[0], the address of the 64 char name to be stored in the first key. You can specify 0 if the length value is 0. This name becomes part of the key data. It needs to be unique within the CKDS.
  • &lKey2 value 64. This is the length of the string following – the value of the key name stored in the second key. Set this to zero if you do not want a name stored in the second generated key
  • &KeyName2[0], the address of 64 char name to be stored in the second key. You can specify 0 if the length value is 0.
  • 0, user associated data length for the first key.
  • 0 not used if the length is zero.
  • 0, user associated data length for the second key.
  • 0 not used if the length is zero.
  • 0,
  • – length of the KEK for the first key. Because the rule was OPEX – the first key is to be stored in the local CKDS and so does not have a KEK.
  • 0 this KEK is not used.
  • &lKekName lKekName has the value 64.
  • &KEK[0] this is the address of the first character of the Key Encrypting Key. It must be padded with blanks to 64 characters.
  • &lData1 the value 900 which is the length of the storage pointed to by pData1 for the first generated key. This storage is 900 characters long. If a smaller value is used, I got rc 8 rs 11000 Invalid length for a key token, key, or text field even though the returned data was less than 120 bytes long.
  • pData1 pointer to the first skeleton, which is also the area where the first key is returned. This storage is 900 characters long.
  • &lData2 the value 900, which is the length of the storage pointed to by pData2 for the second generated key. This storage is 900 characters long. The length of the data is returned here. If a smaller value is used, I got rc 8 rs 11000 Invalid length for a key token, key, or text field even though the returned data was less than 120 bytes long.
  • pData2 pointer to the second skeleton, which is also the area area where the second key is returned. This storage is 900 characters long.

Generate a CIPHER key using Key Generate 2 (CSNBKGN2) directly

I used the following parameters in CSNBKGN2. (See below for all of the typical parameters I specify.) The character values have been formatted for display, they are 8 byte characters, padded on the left with blanks.

  1. rule = {“AES”,”OP” }
  2. KeyType1 = “CIPHER”
  3. keyBitLength 256
  4. kek lengths = 0; no KEK is used (because it will be used/stored on this system)

This created a key, but I could not use it because it defaults to Key Usage CBC, when ANY_MODE is required for data set encryption.

Generate a CIPHER key using a skeleton and Key Generate 2

You need to use Key Token Build CSNBKTB2 if you want to use any of the non default options. For example

Key Token Build CSNBKTB2 parameters

  1. rule_count = 5;
  2. rule = {“EXTERNAL”,”AES “,”CIPHER “, “XPRTCPAC”,”ANY-MODE” }

The “XPRTCPAC”,”ANY-MODE” parameters are required if data set encryption is used

CSNBKTB2 returns an internal format key, which can be used by CSNBKGN2.

CSNBKGN2 parameters

  1. rule_count = 2;
  2. rule = {“AES “,”OP “};
  3. keyType1 = “TOKEN”. This says use the skeleton value in the generated_key_identifier_1 field.
  4. kek lengths = 0; no KEK is used (because it will be used/stored on this system).
  5. generated_key_identifier_1_length 900 (in this case).
  6. generated_key_identifier_1 address of my skeleton data.
  7. generated_key_identifier_2_length 0 – we are not generating a second parameter.
  8. generated_key_identifier_2 0 – this value is ignored.

Generation two CIPHERs one for local, one for remote.

To generate the skeletons

  1. Rule_count = 5;
  2. Rule1 = {“INTERNAL”,”AES “,”CIPHER “, “XPRTCPAC”,”ANY-MODE” }
  3. Rule2 = {“EXTERNAL”,”AES “,”CIPHER “, “XPRTCPAC”,”ANY-MODE” }

CSNBKGN2 parameters

  1. pOPRule = {“AES”,”OPIM “};
  2. keyType1 = “TOKEN”. This says use the skeleton value in generated_key_identifier_1. Skeleton 1 was defined with CIPHER.
  3. keyType2 = “TOKEN”. This says use the skeleton value in generated_key_identifier_2. Skeleton 2 was defined with CIPHER.
  4. kek length1 = 0; no KEK is used (because it will be used/stored on this system).
  5. kek length2 = 64 ; A KEK is used. The name of an IMPORTER KEK.
  6. generated_key_identifier_1_length 900
  7. generated_key_identifier_1 address of my skeleton data, and where the data is returned.
  8. generated_key_identifier_2_length 900.
  9. generated_key_identifier_2 address of my skeleton data, and where the data is returned.

I created one OPerational copy which I wrote to the local PKDS, and an encrypted copy which I wrote to a data set using fwrite(). On the remote system I used fread() toread the file, and used the IMPORTER key to import it into the PKDS. This is the OPIM in the rules.

Generating an importer/exporter pair

In the examples above they just generated a CIPHER for the local system. You can use CSNBKGN2 to generate a key an IMPORTER/EXPORTER pair.

It creates one of the keys in a form that can be added directly to the CKDS, and creates the opposite key, encrypted by a KEK. This means you can write it to a dataset, send the data set to the remote system, and import it using the KEK’s importer key.

To generate the skeletons

  1. Rule_count = 3;
  2. Rule1 = {“INTERNAL”,”AES “,”IMPORTER” }
  3. Rule2 = {“EXTERNAL”,”AES “,”EXPORTER” }

CSNBKGN2 parameters

  1. pOPRule = {“AES”,”OPEX”};
  2. keyType1 = “TOKEN”. This says use the skeleton value. Skeleton 1 was defined with EXPORTER.
  3. keyType2 = “TOKEN”. This says use the skeleton value. Skeleton 2 was defined with IMPORTER
  4. kek length1 = 0; no KEK is used (because it will be used/stored on this system).
  5. kek length2 = 64 ; A KEK is used. The name of an EXPORTER KEK.
  6. generated_key_identifier_1_length 900
  7. generated_key_identifier_1 address of my skeleton data, and where the data is returned.
  8. generated_key_identifier_2_length 900.
  9. generated_key_identifier_2 address of my skeleton data, and where the data is returned.

I used the ICSF function CSNBKRC2 to write the first generated key to the CKDS.

I wrote the second generated key to a file using fwrite() . I could send this file to a remote site, then use CSNDSYI2 with the IMPORTER version of the KEK to process the data into a key for local use, then write it to the CKDS using CSNBKRC2.

Which options produce what output?

  • With EX in the rule OPEX, and an EXPORTER Key Exporting Key it worked.
  • With IM in the rule OPIM, and an EXPORTER key, I got Key not found, because it was searching for keyname of type IMPORTER, when only key name with type EXPORTER was present.
  • With IM in the rule OPIM, and an IMPORTER key, I got reason code 10040 Key identifiers contain a version number.
  • You can use OPIM for CIPHER keys, but not IMPORTER or EXPORTER. See the list of valid combinations.

Setting up ICSF on z/OS

ICSF provides many functions. One function is to manage keys encryption and decryption.

As well as providing encryption etc, it can provide tokens to access encryption keys.

  • ICSF public and private keys are stored in the Public Key Data Set (PKDS)
  • ICSF Cryptographic keys that are encrypted are stored in the Cryptographic Key Data Set(CKDS)
  • ICSF PKCS #11 tokens and objects are stored in Token Key Data Set

There is a batch interface for some command, and ISPF interface using @ICSF, and an API.

The instructions below are a “get you going”. You can protect you keys see Maintaining cryptographic keys.

Create the datasets

Create the CKDS

//IBMCKDS   JOB 1 
//DEFINE EXEC PGM=IDCAMS,REGION=4M
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE 'COLIN.SCSFCKDS'
DEFINE CLUSTER (NAME(COLIN.SCSFCKDS) -
VOLUMES(C4SYS1) -
RECORDS(200 100) -
RECORDSIZE(372,32756 ) -
KEYS(72 0) -
FREESPACE(10,10) -
SHAREOPTIONS(2,3)) -
DATA (NAME(COLIN.SCSFCKDS.DATA) -
BUFFERSPACE(100000) -
ERASE) -
INDEX (NAME(COLIN.SCSFCKDS.INDEX))
/*

Create the PKDS

//IBMPKDS   JOB 1 
//DEFINE EXEC PGM=IDCAMS,REGION=64M
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE 'COLIN.SCSFPKDS' PURGE
DEFINE CLUSTER (NAME(COLIN.SCSFPKDS) -
VOLUMES(C4SYS1) -
RECORDS(100 50) -
RECORDSIZE(800,32756) -
KEYS(72 0) -
FREESPACE(0,0) -
SHAREOPTIONS(2,3)) -
DATA (NAME(COLIN.SCSFPKDS.DATA) -
BUFFERSPACE(100000) -
ERASE -
CISZ(32768)) -
INDEX (NAME(COLIN.SCSFPKDS.INDEX))
/*

Set up the TKDS

This is described here.

The documentation describes 2 ways of creating the TKDS.

  • Define
  • Define and initialise it

I do not know what the difference is. The first seems to work OK

//IBMCKDS   JOB 1 
//DEFINE EXEC PGM=IDCAMS,REGION=4M
//SYSPRINT DD SYSOUT=*
//SYSIN DD *
DELETE 'COLIN.SCSFTKDS'
DEFINE CLUSTER (NAME(COLIN.SCSFTKDS) -
VOLUMES(C4SYS1) -
RECORDS(100 50) -
RECORDSIZE(2200,32756) -
KEYS(72 0) -
FREESPACE(0,0) -
SPANNED -
SHAREOPTIONS(2,3)) -
DATA (NAME(COLIN.SCSFTKDS.DATA) -
BUFFERSPACE(100000) -
CONTROLINTERVALSIZE(32768) -
ERASE) -
INDEX (NAME(COLIN.SCSFTKDS.INDEX))
/*
/*

Update the parmlib member for ICSF

The ICSF job output has information like

IEE252I MEMBER CSFPRMCP FOUND IN USER.Z31A.PARMLIB

Either use this member, or create a new member.

My member has

CKDSN(COLIN.SCSFCKDS)                        
PKDSN(COLIN.SCSFPKDS)
TKDSN(COLIN.SCSFTKDS)
DOMAIN(0)

See Parameters in the installation options data set.

Running on zPDT and ADCD, I also had to specify SSM(YES) so I could specify KGUP parameter CLEAR.

Check your IKJTSOxx has

AUTHPGM NAMES(          /* AUTHORIZED PROGRAMS      */      
...
CSFDAUTH /* ICSF COMMAND */ +
CSFDPKDS /* ICSF Command */
...
)

Started task member CSF

In the ICSF documentation the started task is called CSF. Personally I would have called it ICSF.

//CSF  PROC PRM=CP 
//CSF EXEC PGM=CSFINIT,PARM=&PRM,REGION=0M,TIME=1440,MEMLIMIT=NOLIMIT

RACF Started definition

On my system it uses the default STARTED * (G) profile. This has

STDATA INFORMATION          
------------------
USER= START1
GROUP= SYS1
TRUSTED= NO
PRIVILEGED= NO
TRACE= YES

RACF user definitions

RDEFINE CSFSERV CSF* UACC(NONE) 
RDEFINE CSFSERV CSF1TRL UACC(NONE)
RDEFINE CSFSERV CSF1TRC UACC(NONE)
PERMIT CSF* CLASS(CSFSERV) ID(PKISRVD) ACCESS(READ)
PERMIT CSF1TRL CLASS(CSFSERV) ID(PKISRVD) ACCESS(READ)
PERMIT CSF1TRL CLASS(CSFSERV) ID(COLIN ) ACCESS(READ)
PERMIT CSF1TRL CLASS(CSFSERV) ID(IBMUSER) ACCESS(READ)
PERMIT CSF1TRC CLASS(CSFSERV) ID(IBMUSER) ACCESS(READ)
SETROPTS CLASSACT(CSFSERV) RACLIST(CSFSERV)
SETROPTS RACLIST(CSFSERV,CRYPTOZ) REFRESH

Start ICSF

S  CSF

This gave me

CSFO0230 CKDSN(COLIN.SCSFCKDS)                                
CSFO0230 PKDSN(COLIN.SCSFPKDS)
CSFO0230 TKDSN(COLIN.SCSFTKDS)
CSFO0230 DOMAIN(0)
CSFO0166 DEFAULT CICS WAIT LIST WILL BE USED.
IEE252I MEMBER CTICSF00 FOUND IN SYS1.PARMLIB
CSFM607I A CKDS KEY STORE POLICY IS NOT DEFINED.
CSFM607I A PKDS KEY STORE POLICY IS NOT DEFINED.
CSFM610I GRANULAR KEYLABEL ACCESS CONTROL IS DISABLED.
CSFM611I XCSFKEY EXPORT CONTROL FOR AES IS DISABLED.
CSFM611I XCSFKEY EXPORT CONTROL FOR DES IS DISABLED.
CSFM612I PKA KEY EXTENSIONS CONTROL IS DISABLED.
CSFM654I KEY ARCHIVING USE CONTROL IS DISABLED.
CSFM723I ARCHIVED KEY USE DATA DECRYPTION CONTROL IS DISABLED.
CSFM732I MASTER KEY ENTRY UTILITY KEY OWNERSHIP CONTROL IS DISABLED.
CSFM653I CKDS LOADED 3 RECORDS WITH AVERAGE SIZE 254
CSFM653I TKDS LOADED 45 RECORDS WITH AVERAGE SIZE 2083
CSFM129I MASTER KEY DES ON CRYPTO EXPRESS8 COPROCESSOR 8C00, SERIAL
NUMBER 93AAC740, IS CORRECT.
CSFM129I MASTER KEY AES ON CRYPTO EXPRESS8 COPROCESSOR 8C00, SERIAL
NUMBER 93AAC740, IS CORRECT.
CSFM129I MASTER KEY RSA ON CRYPTO EXPRESS8 COPROCESSOR 8C00, SERIAL
NUMBER 93AAC740, IS CORRECT.
CSFM129I MASTER KEY ECC ON CRYPTO EXPRESS8 COPROCESSOR 8C00, SERIAL
NUMBER 93AAC740, IS CORRECT.
CSFM111I CRYPTOGRAPHIC FEATURE IS ACTIVE. CRYPTO EXPRESS8 COPROCESSOR
8C00, SERIAL NUMBER 93AAC740.
CSFM129I MASTER KEY DES ON CRYPTO EXPRESS8 COPROCESSOR 8C01, SERIAL
NUMBER 93AAC741, IS CORRECT.
CSFM129I MASTER KEY AES ON CRYPTO EXPRESS8 COPROCESSOR 8C01, SERIAL
NUMBER 93AAC741, IS CORRECT.
CSFM129I MASTER KEY RSA ON CRYPTO EXPRESS8 COPROCESSOR 8C01, SERIAL
NUMBER 93AAC741, IS CORRECT.
CSFM129I MASTER KEY ECC ON CRYPTO EXPRESS8 COPROCESSOR 8C01, SERIAL
NUMBER 93AAC741, IS CORRECT.
CSFM111I CRYPTOGRAPHIC FEATURE IS ACTIVE. CRYPTO EXPRESS8 COPROCESSOR
8C01, SERIAL NUMBER 93AAC741.
CSFM133I THERE ARE NO ACTIVE PKCS11 COPROCESSORS.
CSFM508I CRYPTOGRAPHY - THERE ARE NO CRYPTOGRAPHIC ACCELERATORS ONLINE.
CSFM698I DOMAIN IN USE: 0
CSFM015I FIPS 140 SELF CHECKS FOR PKCS11 SERVICES SUCCESSFUL.
CSFM400I CRYPTOGRAPHY - SERVICES ARE NOW AVAILABLE.
CSFM130I CRYPTOGRAPHY - DES SERVICES ARE AVAILABLE.
CSFM130I CRYPTOGRAPHY - RSA SERVICES ARE AVAILABLE.
CSFM130I CRYPTOGRAPHY - ECC SERVICES ARE AVAILABLE.
CSFM127I CRYPTOGRAPHY - AES SERVICES ARE AVAILABLE.
CSFM126I CRYPTOGRAPHY - FULL CPU-BASED SERVICES ARE AVAILABLE.
CSFM001I ICSF INITIALIZATION COMPLETE
CSFM640I ICSF RELEASE FMID=HCR77E0.
CSFM716I ICSF HAS BEEN INITIALIZED WITH SCSFMOD0 FROM LNKLST
CSFM716I ICSF HAS BEEN INITIALIZED WITH SIEALNKE FROM LNKLST

Message CSFM133I THERE ARE NO ACTIVE PKCS11 COPROCESSORS is because zPDT does not support this.

To stop ICSF use the operator command

p CSF

Initialise the environment

Get into the ICSF environment.

  • From ISPF option 6 (TSO)
  • @ICSF
  • panelid on to display the name of the panel in the top left corner of the screen
  • you should be in panel CSF@PRIM
  • option 6 PPINIT – Pass Phrase Master Key/KDS Initialization -> panelid CSFPMC40
  • Enter your pass phrase, tab down to _ Reinitialize system and select it using /
  • Enter your data set names for example CKDS ===> ‘COLIN.SCSFCKDS’ . Ensure the name is in quotes.
  • press enter. I got a panel with message ‘ARE YOU SURE YOU WISH TO PROCEED WITH PASS PHRASE INITIALIZATION?’
  • press enter to continue.
  • The message CSFM653I PKDS LOADED 6 RECORDS WITH AVERAGE SIZE 583 was displayed and INITIALIZATION COMPLETE was displayed in the top right corner.

Update your backup procedure to backup these datasets.

What can you do once it has started?

You can issue operator commands

d icsf,kds                                            
CSFM668I 14.33.27 ICSF KDS 319
CKDS COLIN.SCSFCKDS
FORMAT=KDSR SYSPLEX=N MKVPs=DES AES
DES MKVP date=2024-09-10 12:43:29
AES MKVP date=2024-09-10 12:43:29
PKDS COLIN.SCSFPKDS
FORMAT=KDSR SYSPLEX=N MKVPs=RSA ECC
RSA MKVP date=2024-09-10 12:43:30
ECC MKVP date=2024-09-10 12:43:30
TKDS COLIN.SCSFTKDS
FORMAT=KDSRL SYSPLEX=N MKVPs=None

You can use the @ICSF TSO command (see above)

  • To list the CKDS use 5 UTILITY; 5 CKDS KEYS;1 List all records
  • To list the PKDS use 5 UTILITY; 6 PKDS KEYS;1 List all records
  • To list the TKDS use 5 UTILITY; 7 tokens in TKDS ;4 List existing tokens

Note: Be careful when using the ISPF line commands as the D means delete, not display!

Batch programs

You can use The Key Generation Utility Program CSFKGUP. This uses definitions described here. See One minute MVS – Using individual data set encryption on z/OS for an example of defining a key.

Whoops

After I had used PKI Services to define certificates, I got the health checker message CSFH0054I Check for clear keys in the CKDS, PKDS, and TKDS.

The problems were

Active TKDS: COLIN.SCSFTKDS 
---------------------------------------------
PKISRVD.PKITOKEN 00000001T
PKISRVD.PKITOKEN 00000002T
PKISRVD.PKITOKEN 00000003T
PKISRVD.PKITOKEN 00000004T

Which came from PKISERVD

I fixed this changing the PKI Server pkiserv.conf file to have SecureKey=T, but zPDT does not support encrypted keys, so with zPDT you have to have SecureKey=F.
I got messages in the PKISERVD log

IKYC010I Error 791740530 returned from …: PKI Services can not generate certificates with secure keys.

Setting up ICSF security for commands and APIs

The documentation for ICSF and securing access to keys is hard to follow. There is a lot of it, but is not easily consumed.

There are two orthogonal aspects of ICSF security

  • Giving permission for a userid to use CSFKGUP utility, the ICSF ISPF panels, or the ICSF API. For example they can use list, but not define.
  • Give permissions to groups of keys. You can define keys with names like DSENCR* have more controls that ANYONE*

There are different levels of granularity for ICSF security.

  • The default is no security – you need to define profiles to protect ICSF resources
  • There is on|off security. If you have any security you can do anything
  • Then there is granular security. You can control down to the operation type.

This blog post is about setting up security for ICSF before you start defining keys.

ICSF security for commands and APIs

The approach to security for ICSF seems to be every one has access unless you take action to define security!

The CSFKGUP utility.

There is a master switch – if this profile exists then do more checks. If this profile does not exist any userid can use CSFKGUP commands to maintain keys. You need to set up Optional SAF checking for KGUP.

RDEFINE XFACILIT CSF.KGUP.VERB.AUTHORITY.CHECK UACC(NONE) 
SETR RACLIST(XFACILIT) REFRESH

RDEFINE CSFSERV CSFKGUP UACC(NONE) WARNING
PERMIT CSFKGUP CLASS(CSFSERV) id(IBMUSER) ACCESS(READ)
SETR RACLIST(CSFSERV ) REFRESH
/*

With the above security setup anyone with read access to the CSFKGUP in class(CSFSERV) gets access to all of the CSFKGUP commands (not just read operations).

This is another master switch, to check the keys security. Its presence or absence controls its use. I had security set up for a key, and with warning mode, which allows the request, and gives a warning message on the console.

RDEFINE XFACILIT CSF.KGUP.CSFKEYS.AUTHORITY.CHECK UACC(NONE)
SETR RACLIST(XFACILIT) REFRESH

Now when I try to delete key SECKEY2 I got

ICH408I USER(IBMUSER ) GROUP(SYS1 ) ...
SECKEY2 CL(CSFKEYS )
WARNING: INSUFFICIENT AUTHORITY - TEMPORARY ACCESS ALLOWED
FROM SEC* (G)
ACCESS INTENT(CONTROL) ACCESS ALLOWED(NONE )

and when I defined a key SECKEY2

ICH408I USER(COLIN   ) GROUP(SYS1    ) NAME(###################
SECKEY2 CL(CSFKEYS )
WARNING: INSUFFICIENT AUTHORITY - TEMPORARY ACCESS ALLOWED
ACCESS INTENT(CONTROL) ACCESS ALLOWED(NONE )

If there is no matching CSFKEYS profile, the userid will be allowed access. When I defined a key ZZZKEY, it worked because there was no matching security profile.

You should define a minimum of

RDEFINE CSFKEYS   C*      AUDIT(ALL) 
PERMIT * CLASS(CSFKEYS) ID(COLIN ) ACCESS(READ)
SETR RACLIST(CSFKEYS ) REFRESH

ISPF ICSF access

You can control which userids can manage ICSF using the ISPF interface. Behind the ISPF interface is code which issues ICSF APIs. You can protect the API calls.

You need to define a resource like

RDEFINE CSFSERV CSFKDSL .... 

and give the userid read access to it.

FunctionThe resources needed
List the contents of a PKDS or CKDSCSFKDSL – list
CSFKDMR – read meta data
CSFKRR2 – read record
Define an AES KeyCSFKRR2 – read record
CSFKGN – key generate
CSFKGN2 – key generate 2
CSFKRC2 -record create 2
CSFKRD – delete record
List the contents of a CKDS elementCSFBRCK
List the contents of a PKDS elementCSFBRPK
Delete a CKDS recordCSFBRCK – control access

The functions and the SAF resource names are defined here.

To define a symmetric cipher key, I needed CONTROL access to the CLASS(CSFKEYS) profile for the key.

Setting up ICSF security for access to keys.

The documentation for ICSF and securing access to keys is hard to follow. There is a lot of it, but is not easily consumed. The security model has evolved over time, and I found the definitions confusing.

There are two orthogonal aspects of ICSF security

  • Giving permission for a userid to use CSFKGUP utility, the ICSF ISPF panels, or the ICSF API. For example they can use list, but not define.
  • Give permissions to groups of keys. You can define keys with names like DSENCR* have more controls that ANYONE*.

There are different levels of granularity for ICSF security.

  • The default is no security – you need to define profiles to protect ICSF resources
  • There is on|off security. If you have any security you can do anything
  • Then there is granular security. You can control down to the operation type.

This blog post is about setting up security to protect keys.

“Required” definitions

You should define the profile below to give you granular controls.

RDEFINE XFACILIT CSF.CKDS.TOKEN.CHECK.LABEL.FAIL 
SETROPTS RACLIST(XFACILIT) REFRESH

If the profile CSF.CKDS.TOKEN.CHECK.LABEL.FAIL is defined, a userid needs the appropriate access to the CSFKEYS profile:

  • READ authority to read a label from the CKDS,
  • UPDATE authority if creating a label,
  • CONTROL authority if writing to or deleting a label.

A userid will also need access to the ICSF commands or APIs to be able to use the key.

If the CSF.CKDS.TOKEN.CHECK.LABEL.FAIL profile is not defined, and the userid has at least read access to a profile for the key, the userid can create, write or delete a label. If there is no profile for the key the userid has access to the key.

If you define the profile RDEFINE XFACILIT CSF.CKDS.TOKEN.CHECK.LABEL.WARN instead of CSF.CKDS.TOKEN.CHECK.LABEL.FAIL, and the userid has at least read access to a profile, the userid can create, write or delete a label. In addition it produces a warning message.

This feels like the same as RDEFINE XFACILIT CSF.CKDS.TOKEN.CHECK.LABEL.FAIL WARNING, so I do not see why RDEFINE XFACILIT CSF.CKDS.TOKEN.CHECK.LABEL.WARN is needed.

“Required” access control for exporting keys.

If you want to control who can export AES and DES keys you can enable this by defining a profile

RDEFINE XFACILIT CSF.XCSFKEY.ENABLE.AES
RDEFINE XFACILIT CSF.XCSFKEY.ENABLE.DES
SETROPTS RACLIST(XFACILIT) REFRESH

This turns on the checking system wide, you do not need to give any userid access to it.

To protect the export of all keys,

RDEFINE XCSFKEY * UACC(NONE)
PERMIT * CLASS(XCSFKEY) ID(SECADMN) ACCESS(UPDATE)
SETROPTS RACLIST(XFCSFKEY) REFRESH

You can generate individual or generic profiles for specific keys.

This security model feels broken, as I can read a key from the CKDS (encrypted with the hardware keys) write it to a file. Send the file to a remote system, and if they remote system has the same hardware keys, write the key to the CKDS. I still need access to read and write the key – but I do not use the XCSFKEY profile.

Defining profiles

The easy bits

You can define a standard security resource, such as

RDEFINE CSFKEY COLINSKEY ...
PERMIT  COLINSKEY CLASS(CSFKEY) ID(...) ACCESS(...)

Where COLINSKEY is the label of an entry in the CKDS or the PKDS. This can be a CIPHER key or a KEK key.

If there is no matching profile, there are no restrictions, so you may want to define a default

RDEFINE CSFKEY * UACC(NONE) AUDIT(ALL) warning

Where AUDIT(ALL) says report every time it is used, and WARNING reports if there is a security violation and gives access anyway. You can then use the information to create more specific profile. Once you have configured security you can remove the warning option, and protect your resources.

You need to specify access:

  • READ to be able to read the profile
  • UPDATE authority if creating a label,
  • CONTROL authority if writing to or deleting a label.

But see “Required” definitions and the CSF.CKDS.TOKEN.CHECK.LABEL.FAIL to enable the granular access. Without the CSF.CKDS.TOKEN.CHECK.LABEL.FAIL profile, if a userid has at least read access to the CSFKEY profile, it can do anything with it. If there is no profile for the key, any userid can do anything with the key.

Which profile is used?

There are different ways of using a key (especially with APIs)

  1. Use the label character string of a key in the CKDS. You can pass a label into an API request. The label name is used in the security checks.
  2. You can read the token from the CKDS using the label, and pass the token (a long string of internal format data) to an API request. ICSF compares the token with the CKDS and if it locates a record with the same key value – it uses the label of the record in the security checks.
  3. You can get the token from elsewhere, for example read a data set. The same checks are done as in 2. above.
  4. If a record with the same key value was not found in the CKDS, (so you are using a key which your CKDS does not know about)
    • If the profile CSF.CKDS.TOKEN.CHECK.LABEL.WARN or CSF.CKDS.TOKEN.CHECK.LABEL.FAIL in class(XFACILIT) is defined, and profile CSF.CKDS.TOKEN.CHECK.DEFAULT.LABEL in class(XFACILIT) is defined, then check the userid’s permission to profile CSF-CKDS-DEFAULT in class(CSFKEYS). See here.

What access do I need to the profile?

If you have CONTROL access to CSFBRCK CLASS(CSFSERV), and CONTROL access to the key’s profile SECKEY2 CL(CSFKEYS) you can delete the key SECKEY2.

If you have ALTER (more powerful than CONTROL) access to CSFBRCK CLASS(CSFSERV), the CSFKEYS profile is ignored you can delete any key.

You might want to give administrator only CONTROL access to the commands and APIs.