My mini project was to connect an openssl client to z/OS with AT-TLS only using a certificate. This was a challenging project partly because of the lack of a map and a description of what to do.
Overview
The usual way a server works with TCP/IP is using socket calls; socket(), bind(), listen() accept(), recv() and send(). You control the socket using ioctl().
This does not work with AT-TLS because ioctl() does not support the AT-TLS calls SIOCTTLSCTL; PL/I, REXX and Assembler supports it, but not C. (See here for a list of supported requests in C). I had to use a lower level set of interfaces (z/OS callable services); BPX1SOC(), BPX1BND(), BPX1LSN(), BPX1ACP(), BPX1RCV(), BPX1SND() and BPX1IOC1()
The documentation says
The application must have the ApplicationControlled parameter set to ON in their TTLSEnvironmentAdvancedParms or TTLSConnectionAdvancedParms statement. This causes AT-TLS to postpone the TLS handshake. After the connection is established, the application can issue the SIOCTTLSCTL IOCTL to get the current AT-TLS connection status and determine whether or not AT-TLS support is available on this connection.
Once the TLS session has been established, you can retrieve the userid associated with the certificate, or you can extract the client’s certificate and process it.
Once you have the userid or certificate you can use the pthread_security_applid_np(__CREATE_SECURITY_ENV…) to change the thread to a different userid. Note you have to run this as a thread – not as the main task.
The application flow
The application has the following logic
Main program
- create a thread using pthread_create
- wait for the thread to end – using rc =pthread_join
- return
Thread subtask
- Allocate a socket using bpx1soc.
- Set the socket so it can quickly be reused. By default a port cannot be reused for a period of minutes, while waiting for a response from the client.
- Bind the port to listen on to this socket using bpx1bnd.
- Listen(wait for) a connection request on this socket using bpx1lsn.
- Accept the request, using bpx1acp.
- Issue the ioctl request using bpx1ioc to query information about the connection (TTLS_QUERY_ONLY). It returned:
- Policy:Policy defined for connection – AT-TLS enabled and Application Controlled.
- Type :Connection is not secure.
- SSL Protocol Version 0 – because the session has not been established.
- SSL Protocol Modifier 0 – because the session has not been established.
- Rule name COLATTLJ.
- Group Action TNGA.
- Environment TNEA.
- Connection TNCA.
- Note: asking for TTLSK_Host_Status gave me “EDC5121I Invalid argument.” because this request is meaningless at this time.
- Issue the ioctl request using BPX1IOC to start the the connection (TTLS_INIT_CONNECTION). This initiates the TLS handshake. If this is successful, it returned in the ioc control block
- Policy:Policy defined for connection – AT-TLS enabled and Application Controlled
- Type :Connection is secure
- SSL Protocol Version 3
- SSL Protocol Modifier 3
- SSL Cipher 4X. 4X means look at the 4 byte field
- SSL Cipher C02C. C02C is
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, which is “256-bit AES in Galois Counter Mode encryption with 128-bit AEAD message authentication and ephemeral ECDH key exchange signed with an ECDSA certificateit AES in Galois Counter Mode encryption”
- userid COLIN
- Receive the data BPX1RCV(). You can peek to see how much data is available using flags = MSG_PEEK
- Send a response using BPX1SND()
- Close the remote session using BPX1CLO
- Close the server’s socket using BPX1CLO
- Exit the thread using pthread_exit()
Mapping certificate to userid
You can use AT-TLS to use the certificate and return the userid associated with the certificate; or you can use the pthread_security_applid_np to pass a certificate and change the thread to be the certificate owner.
You map a certificate to a userid with commands like
//COLRACF JOB 1,MSGCLASS=H //S1 EXEC PGM=IKJEFT01,REGION=0M //SYSPRINT DD SYSOUT=* //SYSTSPRT DD SYSOUT=* //SYSTSIN DD * RACDCERT LISTMAP ID(COLIN) RACDCERT DELMAP(LABEL('CP')) ID(COLIN) RACDCERT MAP ID(COLIN ) - SDNFILTER('CN=docec256.O=Doc.C=GB') - IDNFILTER('CN=SSCA256.OU=CA.O=DOC.C=GB') - WITHLABEL('CP') RACDCERT LISTMAP ID(COLIN) SETROPTS RACLIST(DIGTNMAP, DIGTCRIT) REFRESH /*
This associates the certificate with the given Subject Name (SN) value , and the Issuer’s value with the userid COLIN. See Using certificates to logon to z/OS for a high level perspective.
AT-TLS definitions
TTLSRule COLATTLJ { LocalPortRange 4000 # Jobname COLCOMPI # Userid COLIN Direction BOTH TTLSGroupActionRef TNGA TTLSEnvironmentActionRef TNEA TTLSConnectionActionRef TNCA } TTLSRule COLATTLS { LocalPortRange 4000 # Jobname COLATTLS Userid START1 Direction BOTH TTLSGroupActionRef TNGA TTLSEnvironmentActionRef TNEA TTLSConnectionActionRef TNCA } TTLSConnectionAction TNCA { TTLSCipherParmsRef TLS13TLS12 TTLSSignatureParmsRef TNESigParms TTLSConnectionAdvancedParmsRef TNCOonAdvParms CtraceClearText Off Trace 255 } TTLSConnectionAdvancedParms TNCOonAdvParms { ServerCertificateLabel NISTECC521 #ServerCertificateLabel RSA2048 #ccp this was added ApplicationControlled On SSLv3 OFF TLSv1 OFF TLSv1.1 OFF TLSv1.2 ON TLSv1.3 OFF SecondaryMap OFF HandshakeTimeout 3 } TTLSSignatureParms TNESigParms { CLientECurves Any } TTLSEnvironmentAction TNEA { HandshakeRole ServerWithClientAuth # HandshakeRole Server TTLSKeyringParms { Keyring start1/TN3270 } TTLSSignatureParmsRef TNESigParms TTLSCipherParmsRef TLS13 } TTLSCipherParms TLS13TLS12 { #TLS 1.3 V3CipherSuites TLS_CHACHA20_POLY1305_SHA256 #V3CipherSuites TLS_AES_256_GCM_SHA384 #V3CipherSuites TLS_AES_128_GCM_SHA256 #TLS 1.2 # NSTECC V3CipherSuites TLS_RSA_WITH_AES_256_CBC_SHA256 V3CipherSuites TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 #RSA } TTLSCipherParms TLS13 { V3CipherSuites TLS_CHACHA20_POLY1305_SHA256 } TTLSGroupAction TNGA { TTLSEnabled ON trace 255 }
Submitting a job from userid COLIN, got definition Rule name COLATTLJ
When I used a started task COLATTLS with userid START1, I was expecting Rule name COLATTLS but got Rule name COLATTLJ. It looks like PAGENT uses the first matching rule; it matches rule PORT 4000, so used COLATTLJ.
My program printed out
Group Action TNGA. Environment TNEA. Connection TNCA.
which matches the definitions above.
The program printed out
BPX1SOC rv 0 BPX1SOC socket value 0 BPX1OPT SET SO_REUSEADDR rv 0 BPX1BND rv 0 BPX1LSN rv 0 BPX1ACP rv 1 After IOC BPX1IOC rv 0 printATTLS rv 0 query.header.TTLSHdr_BytesNeeded 136 BPX1IOC Policy:Policy defined for connection - AT-TLS enabled and Application Controlled BPX1IOC Conn :Connection is not secure BPX1IOC Type :Server with client authentication ClientAuthType = Required SSL protocol version TTLS_PROT_UNKNOWN Rule name COLATTLJ. Group Action TNGA. Environment TNEA. Connection TNCA. TTLS_INIT_CONNECTION BPX1SOC TTLS_INIT_CONNECTION rv 0 After INIT_CONNECTION printATTLS rv 0 query.header.TTLSHdr_BytesNeeded 128 BPX1IOC Policy:Policy defined for connection - AT-TLS enabled and Application Controlled BPX1IOC Conn :Connection is secure BPX1IOC Type :Server with client authentication ClientAuthType = Required SSL protocol version TTLS_PROT_TLSV1_2 SSL Cipher C02C ioc.TTLSi_Cert_Len 1080 get cert IOC BPX1IOC Get cert rv 0 userid 8 ADCDA pthread_s... applid ZZZ rc = 0 userid ADCDA. BPX1RCV Peek rv 4 BPX1RCV bytes 4 BPX1SND rv 48
AT-TLS programming
In my program I had
Accept the session and invoke ATTLS
struct sockaddr_in client; /* client address information */ BPX1SOC()... BPX1OPT()... // Set SO_REUSEADDR BPX1BND()... BPX1LSN().. // this returns once there is a connection to the socket int lClient = sizeof(client); BPX1ACP(&Socket_vector[0], &lClient, &client, &rv, &rc, &rs); if (check("BPX1ACP",rv,rc,rs) < 0 ) // -1 is error exit(4); int sd = rv; // save the returned value #include <attls.h> #include <attlssta.h>
Issue ATTLS query before initial TLS handshake
Member attls.h had
// AT-TLS struct TTLS_IOCTL ioc; // ioctl data structure memset(&ioc,0,sizeof(ioc)); //* set all unused fields to zero ioc.TTLSi_Req_Type = TTLS_QUERY_ONLY ; int command; command = SIOCTTLSCTL; ioc.TTLSi_Ver = TTLS_VERSION2; int lioc; lioc = sizeof(ioc); // // this is used for getting data from ATTLS // a header and a number of quads // memset(&query,0,sizeof(query)); // move the eye catcher memcpy(&query.header.TTLSHeaderIdent[0],TTLSHeaderIdentifier,8); query.header.TTLSHdr_BytesNeeded = 128; query.header.TTLSHdr_SetCount = 0; query.header.TTLSHdr_GetCount = 4; query.q1.TTLSQ_Key = TTLSK_TTLSRuleName ; query.q2.TTLSQ_Key = TTLSK_TTLSGroupActionName; query.q3.TTLSQ_Key = TTLSK_TTLSEnvironmentActionName ; query.q4.TTLSQ_Key = TTLSK_TTLSConnectionActionName ; ioc.TTLSi_BufferPtr = (char *) &query; ioc.TTLSi_BufferLen = sizeof(query); ioc.TTLSi_Ver = TTLS_VERSION2; BPX1IOC(&sd, &command, &lioc, &ioc , &rv, &rc, &rs); if (check("BPX1IOC",rv,rc,rs) != 0) exit(1); printATTLS( &ioc,rv,rc,rs);
Issue the start connection
Member attlssta.h had
command = SIOCTTLSCTL; ioc.TTLSi_Ver = TTLS_VERSION2; lioc = sizeof(ioc); ioc.TTLSi_Req_Type = TTLS_INIT_CONNECTION ; printf("TTLS_INIT_CONNECTION\n"); // // memset(&query,0,sizeof(query)); // move the eye catcher memcpy(&query.header.TTLSHeaderIdent[0],TTLSHeaderIdentifier,8); query.header.TTLSHdr_BytesNeeded = 128; query.header.TTLSHdr_SetCount = 0; query.header.TTLSHdr_GetCount = 0; ioc.TTLSi_BufferPtr = 0; ioc.TTLSi_BufferLen = 0; //printHex(stdout,&query,256); ioc.TTLSi_Ver = TTLS_VERSION2; BPX1IOC(&sd, &command, &lioc, &ioc , &rv, &rc, &rs); if (check("BPX1SOC TTLS_INIT_CONNECTION ",rv,rc,rs) != 0) exit(1); printATTLS( &ioc,rv,rc,rs); if (rv >= 0) { // get the certificate #include <ATTLSGC.h> }
Get the certificate fromAT-TLS
ATTLSGC.h (get certificate from TCPIP) had
// AT-TLS // // Get the certificate // char * applid = "ZZZ"; memset(&ioc,0,sizeof(ioc)); //* set all unused fields to zero ioc.TTLSi_Req_Type = TTLS_QUERY_ONLY ; int command; command = SIOCTTLSCTL; ioc.TTLSi_Ver = TTLS_VERSION2; int lioc; lioc = sizeof(ioc); // // this is used for getting data from ATTLS // a header and a number of quads // memset(&query,0,sizeof(query)); // move the eye catcher memcpy(&query.header.TTLSHeaderIdent[0],TTLSHeaderIdentifier,8); query.header.TTLSHdr_BytesNeeded = 128; query.header.TTLSHdr_SetCount = 0; query.header.TTLSHdr_GetCount = 1; query.q1.TTLSQ_Key = TTLSK_Certificate ; ioc.TTLSi_BufferPtr = (char *) &query; ioc.TTLSi_BufferLen = sizeof(query); ioc.TTLSi_Ver = TTLS_VERSION2; BPX1IOC(&sd, &command, &lioc, &ioc , &rv, &rc, &rs); printf("ioc.TTLSi_Cert_Len %d \n",ioc.TTLSi_Cert_Len); printf("get cert IOC\n"); if (check("BPX1IOC Get cert",rv,rc,rs) != 0) exit(1);
AT-TLS will return a userid for application OMVSAPPL, which may not be what was wanted.
Use the certificate to change the userid of the thread.
This uses pthread_security_applid_np to the userid determined from the certificate, and the specified applied.
Using userid and password the code is
rc = pthread_security_applid_np(__CREATE_SECURITY_ENV, __USERID_IDENTITY, 5, // length of userid "COLIN", // userid "PASSWORD", // password- null terminated 0,"OMVSAPPL");
Using a certificate is a little more complicated.
// use certificate to change userid char * applid = "ZZZ"; struct __certificate ct; ct.__cert_type = __CERT_X509; char * pData = (char *) ioc.TTLSi_BufferPtr; // offsets are from start of header ct.__cert_length = ioc.TTLSi_Cert_Len; ct.__cert_ptr =& pData[query.q1.TTLSQ_Offset] ; //printHex(stdout,ct.__cert_ptr, 66); rc = pthread_security_applid_np(__CREATE_SECURITY_ENV, __CERTIFICATE_IDENTITY, sizeof(ct), // size of object &ct, // adress of object "xxxxxxxx", // not used with certificate 0, // options applid); // this controls which applid security checks are done. if ( rc != 0) perror("pthead security"); switch (errno) { case ESRCH : printf("ESRCH:" "The user ID provided as input is not defined to the " "security product or does not have an OMVS segment defined" "\n"); break; } if (rc != 0) { lOutBuff = sprintf(&outBuff[0], "pthread_s... applid %s rc = %d errno %d %s errno2 %8.8x\n\n", applid, rc,errno,strerror(errno),__errno2()); } else { userlen = 0; rc = __getuserid(&userid[0], userlen); if (rc != 0) printf("getuser rc %d\n",rc); printf("userid %d %*.*s\n",userlen,userlen,userlen,userid); lOutBuff = sprintf(&outBuff[0], "pthread_s... applid %s rc = 0 userid %*.*s.\n", applid, userlen,userlen,userid); } printf("%s\n",outBuff);
This certificate was mapped to userid ADCDA, and the userid ADCDA was printed. See Using certificates to logon to z/OS-Use a subject DN for the mapping.
Routine to print out the IOC and its data
int printATTLS(struct TTLS_IOCTL * pioc, int rv, int rc, int rs) { if (check("printATTLS",rv,rc,rs) != 0) // check the return code return(8); printf("query.header.TTLSHdr_BytesNeeded %d\n", query.header.TTLSHdr_BytesNeeded); printf("BPX1IOC Policy:%s\n",Stat_Policy[pioc->TTLSi_Stat_Policy]); printf("BPX1IOC Conn :%s\n", Stat_Conn[ pioc->TTLSi_Stat_Conn]); printf("BPX1IOC Type :%s\n", Set_type[ pioc->TTLSi_Sec_Type]); char * pProt = "Unknown Protocol"; switch( pioc->TTLSi_SSL_Prot) { case TTLS_PROT_UNKNOWN: pProt = "TTLS_PROT_UNKNOWN";break; case TTLS_PROT_SSLV2 : pProt = "TTLS_PROT_SSLV2 ";break; case TTLS_PROT_SSLV3 : pProt = "TTLS_PROT_SSLV3 ";break; case TTLS_PROT_TLSV1 : pProt = "TTLS_PROT_TLSV1 ";break; case TTLS_PROT_TLSV1_1: pProt = "TTLS_PROT_TLSV1_1";break; case TTLS_PROT_TLSV1_2: pProt = "TTLS_PROT_TLSV1_2";break; case TTLS_PROT_TLSV1_3: pProt = "TTLS_PROT_TLSV1_3";break; } // printf("SSL Protocol Version %u\n", pioc->TTLSi_SSL_ProtVer); // printf("SSL Protocol Modifier %hhu\n", pioc->TTLSi_SSL_ProtMod); printf("SSL protocol version %s\n",pProt); if (pioc->TTLSi_Neg_Cipher[0] != 0 ) { if ( memcmp(&pioc-> TTLSi_Neg_Cipher[0] ,"4X",2) == 0) printf("SSL Cipher %4.4s\n",pioc->TTLSi_Neg_Cipher4 ); else printf("SSL Cipher %2.2s\n",pioc->TTLSi_Neg_Cipher ); } if (pioc->TTLSi_Neg_KeyShare[0]!= 0) printf("SSL key share %4.4s\n",pioc->TTLSi_Neg_KeyShare ); int lUserid = pioc->TTLSi_UserID_Len; if (lUserid >0 ) { printf("userid %*.*s\n",lUserid,lUserid,&pioc->TTLSi_UserID[0]); } if (pioc->TTLSi_BufferLen > 0 && pioc->TTLSi_BufferPtr > 0) { int len = 256; if (pioc->TTLSi_BufferLen < len) len = pioc->TTLSi_BufferLen; //printHex(stdout,pioc->TTLSi_BufferPtr,len ); char * pData = (char *) pioc->TTLSi_BufferPtr; // offsets are from start of header if (query.q1.TTLSQ_Offset > 0) printf("Rule name %s.\n",&pData[query.q1.TTLSQ_Offset]); else printf("Rule name missing\n"); if (query.q2.TTLSQ_Offset > 0) printf("Group Action %s.\n",&pData[query.q2.TTLSQ_Offset]); else printf("Group Action missing\n"); if (query.q3.TTLSQ_Offset > 0) printf("Environment %s.\n",&pData[query.q3.TTLSQ_Offset]); else printf("Environment missing\n"); if (query.q4.TTLSQ_Offset > 0) printf("Connection %s.\n",&pData[query.q4.TTLSQ_Offset]); else printf("Connection missing\n"); } }
Header file
// used to query data struct { struct TTLSHeader header; struct TTLSQuadruplet q1; struct TTLSQuadruplet q2; struct TTLSQuadruplet q3; struct TTLSQuadruplet q4; struct TTLSQuadruplet q5; char buffer[4096]; } query; // used in printing IOC char * Stat_Policy[]={ "reserved", "AT-TLS function is off", "No policy defined for connection", "Policy defined for connection - AT-TLS not enabled", "Policy defined for connection - AT-TLS enabled", "Policy defined for connection - AT-TLS enabled and " "Application Controlled"}; char * Stat_Conn[] = { "reserved", "Connection is not secure", "Connection handshake in progress", "Connection is secure"}; char * Set_type[] = { "reserved", "Client", "Server", "Server with client authentication " "ClientAuthType = PassThru", "Server with client authentication " "ClientAuthType = Full", "Server with client authentication " "ClientAuthType = Required ", "Server with client authentication " "ClientAuthType = SAFCheck"}; struct TTLS_IOCTL ioc; // ioctl data structure char buff[1000]; // buffer for certificate
Aside on ClientHandshakeSNI
I spent a couple of hours trying to get this to work. I got ServerHandshakeSNIto work.
The documentation says
ClientHandshakeSNI
For TLSv1.0 protocol or later, this keyword specifies whether a client can specify a list of server names. The server chooses a certificate based on that server name list for this connection. For System SSL, the extension ID is set to GSK_TLS_SET_SNI_CLIENT_SNAMES and a flag is set in the gsk_tls_extension structure if it is required. Valid values are:
- Required: Specifies that server name indication support must be accepted by the server. Connections fail if the server does not support server name indication.
- Tip: When you specify ClientHandshakeSNI as required, specify SSLv3 as Off.
- Optional -Specifies that server name indication negotiation is supported, but allows connections with servers that do not support server name indication negotiation.
- Off – Specifies that server name indication is not supported. The function is not enabled. Connections fail if the server requires support for server name indication. This is the default.
I think this only applies when the program on z/OS is running as a client.
Using ServerHandshakeSNI
For example
ServerHandshakeSNI Required ServerHandshakeSNIMatch Required ServerHandshakeSNIList COLINCLIENZ/NISTECC521 ServerHandshakeSNIList CLIENT2/BB
- If my client uses -servername COLINCLIENZ then the certificate with label NISTECC521 will be used.
- If my client uses -servername CLIENT2 then the BB certificate will be used
- Any other server name (or if is omitted) the connection will fail
Verify the sender
The client can use –verify_hostname ZZZZZZ to verify the name of the host.