CS IP filtering: planning the IP filtering rules

To see all my blog posts on IP filtering see here.

I have not configured the rules for a z/OS system, but from my experience of TCP/IP I can see some of the challenges, I hope this section gives you some help in planning your configuration

High level view

There are two approaches to locking down.

  1. Default: allow everything, deny access to specific sites
  2. Default: deny access. Specifically grant access to sites.

The second approach is better. If you “allow everything” by default and deny access to one IP address, the bad guys could use a different address not covered by your rules. If you deny access by default, and only allow the sites you want to use, it makes it harder for the bad guys.

What do I need to think about when planning rules?

From a planning perspective you need to consider several areas

  • The IP addresses of z/OS
  • The ports used by applications on z/OS
  • The IP addresses of client applications connecting to z/OS
  • The ports of used by the clients.
  • Whether to allow or deny access to the resource.

The configuration hierarchy

Information to help you plan your configuration.

A request allowed to pass into z/OS, will usually need a rule to allow the response to return to the requester, flowing out of z/OS. When planning my configuration, in my (paper) notebook, I had a page for requesters to z/OS, and another page for responses, and made sure they tied up.

  • Filter actions: deny|approve
    • Which server IP addresses? Can you create groups of IP addresses which can be reused by different filter rules, for example all LPARS, and all TCP Home addresses for each LPAR
    • Which client IP addresses? Can you create groups of IP addresses which can be reused by different filter rules? For example by country.
    • Within these,
      • Which ports , protocols (TCP/IP,icmp) and direction (inbound or outbound).

What actions should I configure?

There will be some events that will occur, business as usual traffic, which are not interesting, so you would define these so they are not logged.

Other events, such as from new client addresses, are interesting, and you should log these. Once you have put the new rules in place, you can then make them so they are not logged.

I think there are only four actions you should configure when starting to configure IP filtering.

  • IpGenericFilterActionRef permitlog
  • IpGenericFilterActionRef permitnolog
  • IpGenericFilterActionRef denylog
  • IpGenericFilterActionRef denynolog

Where the permitlog entry is defined as

IpGenericFilterAction permitlog 
{
IpFilterAction permit
IpFilterLogging yes
}

and the other filter actions are similar.

I tend to define a rule with permitlog, deploy it, then once it works, change it to permitnolog, and redeploy it, and check the event is not logged -if it is still logged; fix the rule!

You can have “deny” send back an icmp response saying “not allowed due to administrator”. See use of DiscardAction to send an icmp response back.

Background to z/OS IP Addresses

My laptop has a wireless connection, and an Ethernet connection to my server. Each of these interfaces has multiple IP address assigned to it (one IP V4 and multiple IPV6). In a similar way on z/OS each interface (think cable connected into z/OS) has an IP address. For example the TSO NETSTAT HOME command on my system gives me

IntfName:   ETH1                 
Address: 10.1.1.2
Flags: Primary
IntfName: ETH2
Address: 192.168.1.74
Flags:

To the outside world my z/OS has two interfaces ETH1 with IP address 10.1.1.2, and ETH2 with IP address 192.168.1.74.

You might have one interface which is for external facing connections, and another interface for your internal traffic.

For availability and capacity reasons you may have multiple interfaces per LPAR for internal traffic, and similarly for external traffic.

You may have multiple z/OS images and traffic can be routed to any of them. If you want to use global definitions (available to all systems) you need to include all home IP addresses across all interfaces across all z/OS images.

You also have IP V4, and IPV6.

z/OS addresses

You need to identify the IP addresses for those connections coming into z/OS, and the IP addresses of the z/OS systems. The number of z/OS systems should be limited, so you could define a group with the IP addresses of all your z/OS home addresses, across all LPARs.

z/OS ports

For applications running on z/OS, they will typically use one, or a few ports. For example a web server may have a port for TLS connections, and another port for unprotected connections.

If you wanted to gradually remove non TLS connections to your web server, you could create a rule, and say these IP addresses (from this office/area) can no longer use the unprotected port, and so gradually migrate every one to use the TLS connection.

You have to configure the ports inline – you cannot define them once, and refer to them, as you can with IP addresses

Client IP Addresses

Often the client will only have one address, think of wireless connected laptops. It may have a static address, the value of which is always the same, or it may get a dynamic IP address, which is “leased” the duration of the session. For example a DHCP address.

Dynamic addresses may be within a subnet, so for a particular DHCP server, the addresses may be within the range 10.4.5.0-10.4.5.255.

With my Wireless and Ethernet connections, most of the time my system uses the Ethernet connection. If I unplug this, then the wireless connection is used. With this, a client could have multiple addresses into z/OS, and you may need to cover all of them. You could configure it to accept data over my (ultra secure) direct wired Ethernet, but not over the wireless connection, or allow traffic only from a work location, and not from elsewhere.

Client IP port

A server usually has an allocated (well known) IP port. A client application typically uses bind() with port=0) and will then be assigned an unused port from the dynamic range. It can be given a reserved port.

With some products such as MQ Clients you can specify a port range which the application can use. This sort of application typically loops trying ports in the specified range until it finds a free port. You could use this to predefine a range of ports which you can then use IP Filtering to restrict the allowable range.

Baby steps for ICMP

Put your specific rules in front of your generic rules.

You could (I would not) configure rules

 IpFilterRule icmpall  
{
IpSourceAddr all
IpDestAddr all
IpGenericFilterActionRef permitnolog
IpService
{
Protocol icmp
Direction inbound
Routing local
}
}
IpFilterRule icmpspecific
{
IpSourceAddr 10.1.0.2
IpDestAddr all
IpGenericFilterActionRef permitlog
IpService
{
Protocol icmp
Direction inbound
Routing local
}
}

IpGenericFilterAction permitlog
{
IpFilterAction permit
IpFilterLogging yes
}
IpGenericFilterAction permitnolog
{
IpFilterAction permit
IpFilterLogging no
}

If IpFilterRule icmpall is before IpFilterRule icmpspecific the action for all is taken from icmpall; is permit and no log for all traffic – which maybe not what you want.

If these rules are swapped round, so the specific rule is first; traffic for 10.1.0.2 would get permit+log, and other traffic would get permit and no log.

You can then look in the logs to see the traffic, for example

EZD0814I Packet permitted: 12/04/2023 17:18:41.17 filter
rule= icmpinspecific ext= sipaddr= 10.1.0.2 dipaddr=10.1.1.2
proto= icmp(1) type= 8 code= 0 -= Interface=10.1.1.2 (I) secclass= 255 dest= local len= 84 vpnaction= N/A tunnelID= N/A ifcname= ETH1 fragment= N

From the information in the logs, you can refine your rules.

Testing the definitions

You can test your definitions (once you have deployed them) to see what rule is used for source and target IP addresses. See Testing the rules.

CS IP filtering:configuring Defence Manager Daemon (DMD)

To see all my blog posts on IP filtering see here.

With Communications Server(CS) on z/OS you can use filters to manage IP traffic in your TCPIP image. You can allow or deny a IP packet.

You can define

  • Default rules to allow traffic, in TCPIP Profile
  • To define a set of named rules using Policy agent. These can allow or deny access
  • Individual dynamic rules which can be automated, for example external monitors, using DMD

The ipsec command to define dynamic rules. It needs the DMD daemon to be running.

Update the syslogd daemon

You need a statement like

.DMD..* /var/log/DMD.%Y.%m.%d

In the /etc/syslog.conf file.

DMD started task JCL

This was copied from TCPIP.SEZAINST(EZADMD).

//DMD PROC 
//DMD EXEC PGM=DMD,REGION=0K,TIME=NOLIMIT, 
// PARM='ENVAR("_CEE_ENVFILE_S=DD:STDENV")/' 
//* DMD_FILE=/etc/security/dmd.conf 
//* DMD_CTRACE_MEMBER=CTIDMD00 
//* DMD_PIDFILE=/var/dm/dmd.pid 
//* DMD_CODEPAGE=IBM-1047 
//STDENV DD * 
DMD_FILE=//'USER.Z24C.TCPPARMS(DMD)' 
//SYSPRINT DD SYSOUT=* 
//SYSOUT DD SYSOUT=* 

My configuration file for testing is

DMConfig 
{ 
  SyslogLevel 7 
  DefensiveFilterDirectory /var/log/filters 
} 
DmStackConfig TCPIP 
{ 
  Mode  Simulate
} 

I do not know what authority it needs to run. It runs as userid START1 on my ADCD system.

You start it with S DMD .

You stop it using P DMD .

You can display information

f dmd,display

You can tell it to refresh its few lines of configuration.

F DMD,REFRESH
EZD1622I DEFENSE MANAGER DAEMON CONFIGURATION PROCESSING IS COMPLETE USING FILE //'USER.Z24C.TCPPARMS(DMD)'

CS IP filtering: common setup.

To see all my blog posts on IP filtering see here.

You can use the IP filtering component of z/OS Communications Servers to allow or deny packets flowing through a TCPIP stack.

You can use

  • CS default rules – configured in the TCPIP profile, or via an OBEYFILE
  • Policy agent
  • The ipsec command to dynamically add rules – this could be used by networking monitoring tools to defensively add new rules.

These all require common set up.

TCPIP profile

You need in the TCPIP profile

IPCONFIG  IPSECURITY...

Logging is done to the SYSLOGD daemon

You need to have the syslogd daemon running to write any event messages produced.
In /etc/syslog.conf I have

*.err            /var/log/errors 
*.CPAGENT.*.*       /var/log/CPAGENT.%Y.%m.%d 
*.TRMD1.*.info      /var/log/TRMD1I.%Y.%m.%d 
*.DMD.*.*           /var/log/DMD.%Y.%m.%d 
  • My policy agent job is called CPAGENT
  • My Defense Manager Daemon (DMD) is a started task with name DMD
  • I start TRMD, which logs events about the allow/deny rules. TRMD starts up a job TRMD1, and so the syslogd configuration needs to have TRMD1. The messages about allow/deny are written to syslogd as info and debug messages.

Traffic Regulation Management Daemon (TRMD)

This this writes events, about allow or deny packets through the TCPIP stack, to the syslogd daemon.

If this is not running, packets may still get allowed/denied, but will not be logged.

//TRMD      PROC  OPT='-P TCPIP -d 1' 
//* 
//*   IBM Communications Server for z/OS 
//*   SMP/E distribution name: EZATRMDP 
//* 
//*   5650-ZOS Copyright IBM Corp. 1996, 2013 
//*   Licensed Materials - Property of IBM 
//*   "Restricted Materials of IBM" 
//* 
//*   Status = CSV2R1 
//* 
//*   Function: Sample procedure for running the Traffic 
//*             Regulator Management Daemon (TRMD) 
//* 
//TRMD   EXEC PGM=EZATRMD,REGION=4096K,TIME=NOLIMIT, 
//      PARM=('ENVAR("_CEE_ENVFILE_S=DD:STDENV")/&OPT') 
//STDENV   DD * 
RESOLVER_CONFIG=//'ADCD.Z24C.TCPPARMS(TCPDATA)' 
export TZ=GMT0 

This daemon requires READ access to the profile BPX.SUPERUSER CL(FACILITY).

When you start TRMD you get

EZZ8495I TRMD STARTED                            
EZZ8500I TRMD INITIALIZATION COMPLETE            
IEF404I TRMD - ENDED - TIME=11.14.33             
$HASP395 TRMD     ENDED - RC=0000                

but it has started it creates a job with name TRMD1.

When running my TRMD1 the operator command D,A,TRMD1 gave

TRMD1    STEP1    START1   OWT  AO  ...  
                                    WKL=SERVERS  SCL=SRVOMVS  P=1                                        

You stop the TRMD daemon using

P TRMD1

You can display information from the log using the Unix command trmdstat

trmdstat … logname

Where logname is the trmd log from syslogd.

Policy Agent

If you want to define rules using Policy Agent, the Policy Agent job needs to be running. The Policy Agent just passes the rules to TCPIP, it does not do any processing of requests. To make a change to the rules, you change the policy agent files, and use the refresh policy agent command.

My policy agent procedure

//CPAGENT  PROC 
//  SET EN='ENVAR("_CEE_ENVFILE_S=DD:STDENV")' 
//PAGENT   EXEC PGM=PAGENT,REGION=0K,TIME=NOLIMIT, 
//       PARM='&EN/  -l /var/log/pagent.log -i -d 1 ' 
//STDENV   DD DISP=SHR,DSN=USER.Z24C.TCPPARMS(PAGENTEN) 
//SYSPRINT DD SYSOUT=H 
//SYSERR   DD SYSOUT=H 
//SYSOUT   DD SYSOUT=H 
//* 
//CEEDUMP  DD SYSOUT=*,DCB=(RECFM=FB,LRECL=132,BLKSIZE=132) 
// PEND 

Member USER.Z24C.TCPPARMS(PAGENTEN)

has

PAGENT_CONFIG_FILE=//'USER.Z24C.TCPPARMS(PAGENTCF)' 
LIBPATH=/usr/lib 

member PAGENTCF

has

tcpImage TCPIP   //'USER.Z24C.TCPPARMS(PAGENTT)' 

member PAGENTTT has

TTLSConfig //'USER.Z24C.TCPPARMS(PAGENTZ)' FLUSH PURGE 
IpSecConfig  //'USER.Z24C.TCPPARMS(PAGEIPSE)' FLUSH PURGE 

member PAGEIPSE

Started with the minimum configuration

#------------------------------------------------------- 

RACF profiles

IPSEC

See ipsec command SERVAUTH profile

RDEFINE SERVAUTH  EZB.IPSECCMD.*.*.DISPLAY
RDEFINE SERVAUTH  EZB.IPSECCMD.*.*.CONTROL
PERMIT  EZB.IPSECCMD.*.*.DISPLAY CLASS(SERVAUTH) ID(IBMUSER) -
    ACCESS(READ)  
PERMIT  EZB.IPSECCMD.*.*.CONTROL CLASS(SERVAUTH) ID(IBMUSER) -
    ACCESS(READ)
SETROPTS RACLIST(SERVAUTH) REFRESH                 

CS IP Filtering: start here

With Communications Server (CS) on z/OS, which I think of as the TCPIP started task, you can set up packet filtering. You define rules to allow or deny a packet into or out of TCP, based on criteria such as source IP address, destination IP port and address. The action can be to allow the packet into (or out of) TCP/IP, to quietly drop the packet, or to drop the packet but send an icmp response back to the originator. You can also log information about the packet. You could configure the rules so normal traffic is allowed access and not logged, but data from other addresses can be denied, and the event logged. You can review the traffic and set up rules to allow it, or to determine why someone is trying to access your system.

This is know as IPSECurity, and there is a product Vertali zTrust for Networks which interfaces to IPSEC and RACF to manage the environment using standard security tools.

There are three levels of rules

  • Default – these are configured when the TCPIP address space starts – ensuring you do not have a window when there are no rules defined. This might be deny access to all, except the system programmers.
  • From the Policy Agent – this is the normal source of rules.
  • Defensive rules – you can create additional rules on the fly for when there is a problem. These rules can be configured to expire after a time period, for example 30 minutes.

Set up

You have to configure several address spaces, for example:

  • TCPIP – to enable packet filtering, and to define the default(initial) rules.
  • Policy Agent to define the rules used in normal operation.
  • TRMD. This address space takes event information and writes it to the UNIX syslog daemon.
  • SYSLOGD. This is a central service which is sent event data, and the daemon writes the data to one or more files – depending on the configuration and the originating program. Without this events are not logged.
  • DMD – this processes the defensive rules and updates the TCPIP address space.

As usual there are started tasks to create, and define to WLM, security profiles to allow these started tasks to run, and profiles to allow users to administer IPSec packet filtering.

I have written some blog posts to help you navigate the journey. They are written to provide additional information to what is provided by IBM.

Blog posts

CS IP filtering: “standard IP flows”

To see all my blog posts on IP filtering see here.

When setting up rules for IP filtering, I discovered the following, which you might want to define rules for.

Ping

This is an icmp request type 8. The response is an icmp type 0

Traceroute

This can be protocol icmp or udp.

See “Understanding traceroute”

With protocol udp it sends packets to the IP address with different hop count. The first hop is typically port 33434, the second hop is 33435 etc. When traceroute gets the response back, it knows how far the packet got before the hop count went to zero.

To support traceroute, you need to allow IP traffic with ports in the range 33434… the upper limit which is normally less than 33434 + 100.

FTP

See Trying to use FTP.

SFTP

uses port 22.

X3270

You can configure multiple ports in TCPIP, for example one for TLS, and one for non TLS.

You need to have rules for both inbound and outbound traffic specifying the x3270 port.

Internet Group Management Protocol (IGMP)

The Internet Group Management Protocol (IGMP) is a protocol that allows several devices to share one IP address so they can all receive the same data. IGMP is a network layer protocol used to set up multicasting on networks that use the IPv4. Specifically, IGMP allows devices to join a multicasting group.

I had an interface name ETH2 type (IntfType): IPAQENET with IpAddr: 192.168.1.74/0.

I had log records like

source IP Address 192.168.1.254 destination IP address 224.0.0.1
proto=igmp

OMPROUTE

This supports OSPF

When I started OMRPOUTE on z/OS I had a log record with

source IP address 10.1.1.2 destination IP addr= 224.0.0.5 proto= ospf(89)
type= 1 outbound

OMPROUTE also gave a message (every 10 seconds)

EZZ8052I OMPROUTE SEND TO 224.0.0.5 BLOCKED BY TCPIP WHEN USING ETH1

When I started frr on a Linux machine I got a log entry

Source IP address 10.1.1.1 Destination IP address = 224.0.0.5 proto= ospf(89)
type= 1 Interface= 10.1.1.2 Inbound

Which shows it came in over the z/OS interface 10.1.1.2 attached to my Linux machine

CS IP filtering: how to find which rules have been used.

To see all my blog posts on IP filtering see here.

With IP filtering you defined rules to allow or deny to flow in or out of TCP/IP . These rules are defined using the Policy Agent, and you can display information about the rules using the UNIX ipsec command IP filter -f option.

For example the command

ipsec -f display -c current > a

produces a report which includes

FilterName:                   icmpinspecific2 
Type: Generic
Action: Permit
Direction: Inbound
Logging: All
Protocol: ICMP(1)
ICMPType: 8
SourceAddress: 10.1.0.2
DestAddress: 0.0.0.0
DestAddressPrefix: 0
DiscardAction: Silent
FilterMatches: 1

Which shows this filter matched once.

You lose history

If you refresh the policy agent, then the data may be reset – and any statistics lost. You should consider extracting the data, periodically, (such as half hourly), processing it and saving the results for later analysis. Plotting which rules were matched by day, and by time of day may give you insight as to the traffic and provide a base line for when you are investigating incidents.

SMF

SMF 119 subtype 2 records gives information for each connection . I do not know if there is a formatter for the SMF 119 records.

Extracting useful information from the ipsec command

I used the ISPF edit macro na to remove the not applicable stuff, and wrote a macro nafm to hide all the records which have no match.

/* REXX */ 
ADDRESS ISPEXEC
'ISREDIT MACRO'
trace o
"ISREDIT locate .ZFIRST "
do I = 1 by 1
"ISREDIT find 'FilterMatches:'"
if rc <> 0 then leave
"ISREDIT (data)= LINE .ZCSR "
parse var data p1 p2 .
if p2 <> 0 then iterate
/* value is zero so go back and exclude the block */
"ISREDIT find 'FilterName:' prev"
"ISREDIT (f1 ) = LINENUM .ZCSR "
"ISREDIT find '***********' "
"ISREDIT (f2 ) = LINENUM .ZCSR "
do j = f1 to f2 /* suppress the rows */
/* exclude the row */
"ISREDIT XSTATUS " j "= X "
end
end
"ISREDIT locate .ZFIRST "

This gave me

-  -  -  -  -  -  - -  -  77 Line(s) not Displayed 
FilterName: icmpinspecific2
Type: Generic
Action: Permit
Direction: Inbound
Logging: All
Protocol: ICMP(1)
ICMPType: 8
SourceAddress: 10.1.0.2
DestAddress: 0.0.0.0
DestAddressPrefix: 0
DiscardAction: Silent
FilterMatches: 1
***********************************************************************
- - - - - - - - - 25 Line(s) not Displayed

You can now issue commands like “delete all x” to delete the records which had no hits, or “delete all nx” to delete the records which had hits, leaving the unused records.

Converting from TCP/IP devices to interfaces

TCP/IP supports devices for IP V4 using statements like

DEVICE PORTA MPCIPA
  LINK ETH1 IPAQENET PORTA
...
HOME ...
     10.1.1.2 ETH1
...

BEGINRoutes 
...
ROUTE DEFAULT      10.1.1.1 ETH1 MTU 1492 
ENDRoutes 
                                                                   

START PORTA

z/OS TCP has said these DEVICE and LINK statements should be converted these to use the INTERFACE statement, because the LINK and DEVICE statements will be withdrawn in a future release.

The interface statement basically merges the DEVICE, LINK and the relevant part of HOME into one INTERFACE statement. You can use Interface statements for IP V4 and IP V6. They are easier to configure and activate than DEVICE and LINK statements

The equivalent interface statement is

INTERFACE ETH1 
    DEFINE IPAQENET 
    CHPIDTYPE OSD 
    IPADDR 10.1.1.2 
    PORTNAME PORTA         
                 

The command can be written on one (or more lines). You can have

   INTERFACE JFPORTCP4
DEFINE IPAQENET
; this is a comment 
CHPIDTYPE OSD IPADDR 10.1.1.2 PORTNAME PORTA

( Personally I would not; I prefer the command to start in column 1, and following lines indented).

If you name the interface the same as the LINK statement, you will not need to change any routing statements.

The easiest way of implementing the change is to make the change and restart TCP/IP.

The changes you need to make are

  • Replace the DEVICE and LINK statements with the INTERFACE statement
  • Remove the IP address from the HOME
  • Change the START from the port to the interface name

Check it has been defined

After you have restarted TCP/IP

tso netstat home 

gave

MVS TCP/IP NETSTAT CS V2R4       TCPIP Name: TCPIP   
Home address list: 
LinkName:   LOOPBACK 
  Address:  127.0.0.1
    Flags: 
IntfName:   ETH1 
  Address:  10.1.1.2 
    Flags:  Primary 
IntfName:   LOOPBACK6
  Address:  ::1 
    Type:   Loopback 
    Flags: 

The command

tso netstat devlinks 

Gave

IntfName: ETH1              IntfType: IPAQENET   IntfStatus: Ready
    PortName: PORTA     Datapath: 0402     DatapathStatus: Ready 
    CHPIDType: OSD            SMCR: Yes 
    PNetID: *None*            SMCD: Yes 
    TRLE: OSATRL1E 
    Speed: 0000001000 
    IpBroadcastCapability: No 
    CfgRouter: Non                   ActRouter: Non 
    ArpOffload: Yes                  ArpOffloadInfo: Yes 
    CfgMtu: None                     ActMtu: 8992 
    IpAddr: 10.1.1.2/0 
    VLANid: None                     VLANpriority: Disabled 
    ReadStorage: GLOBAL (4096K) 
    InbPerf: Balanced 
    ChecksumOffload: Unsupported     SegmentationOffload: No 
    SecClass: 255                    MonSysplex: No 
    Isolate: No                      OptLatencyMode: No 
  Multicast Specific: 
    Multicast Capability: Yes 
    Group             RefCnt        SrcFltMd 
    -----             ------        -------- 
    224.0.0.1         0000000001    Exclude 
      SrcAddr: None 
  Interface Statistics: 
...
                                                            
IPv4 LAN Group Summary 
LanGroup: 00001 
  Name              Status      ArpOwner          VipaOwner 
  ----              ------      --------          --------- 
  ETH1              Active      ETH1              Yes 
                                                                                                                     
                                                         

where interesting fields are

  • IntfName: ETH1 the interface name
  • IntfType: IPAQENET the interface type
  • IntfStatus: Ready the status of the interface
  • PortName: PORTA the port name
  • Datapath: 0402 what device is being used
  • IpAddr: 10.1.1.2/0 the IP address of the z/OS end of the connection

If you are using DEVICE and LINK the output will have LnkName: ETH1 instead of IntfName: ETH1.

If you are brave…

you can remove the LINK and DEVICE definitions from the active system and activate the INTERFACE, then at a later date, update the TCP/IP configuration file.

You need to

  • stop the existing definition
  • remove the HOST entry for the device
  • delete the DEVICE and LINK from the running configuration
  • activate the interface statement
  • start the interface

Stop the device

V TCPIP,TCPIP,STOP,PORTA

You cannot use V TCPIP,TCPIP,STOP,ETH1 because it says device not found.

Remove the HOME entry for the link

Copy the home statements into a file, remove the entry you do not want, then issue the V…OBEY… on that file. This replaces the active HOME entries.

If you use TSO NETSTAT HOME, the IP address should not be present.

Delete the link and device from the active configuration

One you have removed the HOME statements, and TSO NETSTAT HOME does not show any entries for the device, you can create members DELLINK

DELETE LINK ETH1

and member DELDEV

DELETE DEVICE PORTA

Use the V…OBEY… in turn on each file. You cannot put both commands in one file, as the commands are processed asynchronously and the delete link command may still be executing when the delete device is executed, and then so fail.

The TSO NETSTAT DEVLINKS command should show the link is not in the output.

Activate the interface

Put your interface definition statements into a file and activate it using

V TCPIP,TCPIP,OBEY,USER.Z24C.TCPPARMS(JFACE41)

My definition defines an interface with name JFPORTCP4.

Start the interface

V TCPIP,TCPIP,STA,JFPORTCP4

Test it

TSO NETSTAT HOME should show the IP address, and you should be able to ping it.

You can use TSO NETSTAT DEVLINKS (INTFNAME JFPORTCP4 to display the interface.

Make the change permanent

You will need to:

  • Comment out/remove the PATH and LINK definitions
  • Remove/comment out the IP address and link from the HOME statement
  • Remove the start of the device
  • Add the interface definition. This could be done using an INCLUDE statement
  • Add a start of the interface (or add it to the include file).
  • Use V…OBEY… with the start-up configuration file, or restart TCP/IP
  • Change your documentation!

How to debug a failing AT-TLS connection

AT-TLS provide TLS connection to applications, without changing the application. The IP calls are intercepted and the TLS work is done under the covers.

I was trying to debug a mail server from z/OS to Linux, and could not see any error messages.

In my Policy agent configuration I had

TTLSRule                         CSSMTPRule 
{ 
   RemotePortRange               25 
   Direction                     Outbound 
   TTLSGroupActionRef            CSSMTPGroup 
   TTLSEnvironmentActionRef      CSSMTPEnvironment 
} 
TTLSGroupAction                  CSSMTPGroup 
{ 
   TTLSEnabled                   On 
} 
TTLSEnvironmentAction            CSSMTPEnvironment 
{ 
   HandshakeRole Client 
   TTLSKeyRingParms 
   { 
      Keyring                   START1/MQRING
   } 
    } 
    TTLSEnvironmentAdvancedParms 
    { 
       ApplicationControlled      On 
    } 
 } 

I changed only the Keyring, and left the remainder unchanged. You can specify a TRACE statement, but it looks like it defaults to 2 (Errors are traced to syslogd. The messages are issued with syslogd priority code err).

When I started CSSMTP I got messages

EZD1819I CSSMTP UNABLE TO ESTABLISH A TLS CONNECTION TO TARGET SERVER 10.1.0.2                                                                
EZD1820E CSSMTP NO TARGET SERVER IS CAPABLE OF RECEIVING MAIL AT THIS TIME 

Which were not very helpful.
I had the syslogd daemon running, and it put its output in /var/log. In several TCPIP.* files I had an error message

EZD1286I TTLS Error GRPID: 00000007 ENVID: 00000002 CONNID: 00000036
LOCAL: 10.1.1.2..1032 REMOTE: 10.1.0.2..25 JOBNAME: CSSMTP USERID:
START1 RULE: CSSMTPRule RC: 417 Initial Handshake 0000000000000000
0000005011421D10 0000000000000000

Where the RC is described (0 to 4999) here and 5000 and over described here

If the syslogd daemon is not running, the messages come out on syslog.

Setting up Linux to z/OS certificates

Several times I have had to set up certificates between Linux and z/OS and struggled for a day to get them working. Once you are familiar with doing it – it is easy. As the last time I needed to do this was over a year ago, I’ve forgotten some of the details. This blog post is to help me remember what I need to do, and to help other who struggle with this.

I’m ignoring self signed.

Basic TLS

A certificate contains

  • who it belongs to, such as CN=COLIN,O=SSS
  • the date range the certificate is valid
  • a public key
  • meta data about the key: What algorithm does the public key use, what parameters were used in the key generation, for example, algorithm=RSA, Keysize=2048.

There is a private key.

  • If you encrypt using the private key, you can use the public key to decrypt it.
  • If you encrypt using the public key, you can use the private key to decrypt it.
  • If you encrypt something with my public key, and then encrypt it with your private key. I know it came from you (or someone with your private key) and only I (or someone with my private key) can decrypt it.

Anyone can have the public key. You keep the private key secure.

Certificate Authority. This is used in validating the trust of certificates. You send your certificate to the CA, The CA does a checksum of your data, and encrypts this checksum with the CA private key. It returns your original data appended with the encrypted checksum, and information about the CA, and what was used to calculate the checksum. If someone else has the CA public key, they can do the opposite process. Do the checksum calculation, and decrypt the checksum value in the certificate, using the CA public key. If they match you know it was signed by the CA. This is known as signing the certificate.

To be able to validate a certificate sent to it, the client end needs the CA of the server end. The server needs the CA of the client end to be able to validate the client’s certificate.

During the handshake to establish the TLS connection there is a flow like

  • Establish the cipher spec to use
  • Server sends down its certificate, the client checks it
  • Servers sends down “Certificate request”, and these are the certificate(CAs) I know about
  • The client goes through it’s list of certificates (usually only one), to find the first certificate with a CA in the list sent from the server.
  • sends the client certificate to the server
    • The server checks the certificate. For example the server may be set up to accept a subset of valid algorithms, for example TLS 1.2, and Elliptic Curve. If a certificate is sent up using RSA, then this is not accepted
    • The server checks the signature of the certificate, finds the CA name, checks in the trust store for this CA, and validates the signature. Depending on the application it may check all the CA’s in the CA chain.

What do you need for the handshake to work

  • You need to have a Certificate Authority to sign certificates. In the CA certificate are some flags that say this is a CA.
  • You need to send the public key of each CA to the other end. You normally need to do this just once, and keep using the same certificates for all your TLS work.
  • You need to have a key store/trust store/keyring to hold certificates.
  • On z/OS
    • you may have a keyring for different projects, for example MQ, and TN3270.
    • You need to connect the client CA into each keyring where it will be used.
  • You need to check that the certificates are compatible with the remote end, such as Algorithm etc.

Openssl files

When using openssl, you can store common information in a configuration file. See here. This configuration file has some required options, and some optional options where you can specify common options you frequently use.

If you are using the openssl req command (for example), by default it will look for a section called [req]. This can in turn point to other sections. Using this file you can specify most of your fields in one place, and just override the specific ones.

Create a CA certificate on Linux

I have a bash file docca.sh file on Linux.

CA=”docca256″
casubj=” -subj /C=GB/O=DOC/OU=CA/CN=SSCA256″
days=”-days 1095″
rm $CA.pem $CA.key.pem

openssl ecparam -name prime256v1 -genkey -noout -out $CA.key.pem1

openssl req -x509 -sha384 -config caca.config -key $CA.key.pem2 -keyform pem -nodes $casubj -out $CA.pem3 -outform PEM $days

openssl x509 -in $CA.pem -text -noout|less4

This

  1. creates a private key (docca256.key.pem)
  2. self signs it. For any parameters not specified, it uses the configuration file caca.config and section “req” (signing request) within it.
  3. produces a public certificate in docca256.pem. This file will need to be sent to the backend servers. You can use cut and paste or FTP as ASCII.
  4. displays the x509 data

The caca.config file has

[ req ]
distinguished_name = ca_distinguished_name
x509_extensions = ca_extensions

prompt = no

authorityKeyIdentifier = keyid:always,issuer:always

[ca_distinguished_name ]
# C=GB
# O=DOC
# OU=Stromness
# CN=SSSCA4

####################################
[ ca_extensions ]

subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always
basicConstraints = critical,CA:TRUE, pathlen:0
keyUsage = keyCertSign, digitalSignature,cRLSign

The distinguished_name = ca_distinguished_name says go and look in the file for a section [ca_distinguished_name], and x509_extensions = ca_extensions says go and look for a section called [ca_extensions]. You can specify your own names, for example I could have used section1, and s2.

When prompt = yes, openssl takes as defaults the values in the distinguished_name section. When prompt = no, the distinguished_name is still required – but the contents of the section are ignored.

The values in the x509_extensions are defined here.

Creating an Elliptic Curve certificate on Linux

I used another bash script docecadad.sh, to document an ElliptiCal certificate for userid ADCD. It uses the CA defined above.

name="docecadcd"
key="$name.key.pem"
cert="$name.pem"
subj="-subj /C=GB/O=Doc/CN="$name
CA="docca256"
cafiles="-cert $CA.pem -keyfile $CA.key.pem "

enddate="-enddate 20240130164600Z"
passin="-passin file:password.file"
passout="-passout file:password.file"

rm $name.key.pem
rm $name.csr
rm $name.pem

#define a certificate with elliptic key with size 256

openssl ecparam -name prime256v1 -genkey -noout -out $name.key.pem 
#create a certificate request (ie hello CA please sign this)
openssl req -config openssl.config -new -key $key -out $name.csr -outform PEM -$subj $passin $passout

# sign it.

caconfig="-config ca2.config"
policy="-policy signing_policy"
extensions="-extensions clientServer"

md="-md sha384"

openssl ca $caconfig $policy $md $cafiles -out $cert -in $name.csr $enddate $extensions

# display it 
openssl x509 -in $name.pem -text -noout|less

Where the openssl.config file has

[ req ]
default_bits       = 2048

distinguished_name = server_distinguished_name
req_extensions     = server_req_extensions
string_mask        = utf8only
subjectKeyIdentifier   = hash
#extendedKeyUsage     = critical, codeSigning


[ server_req_extensions ]

subjectKeyIdentifier = hash
# subjectAltName       = DNS:localhost, IP:127.0.0.1, IP:127.0.0.6
# nsComment            = "OpenSSL"
keyUsage             = critical, nonRepudiation, digitalSignature
# extendedKeyUsage     = critical, OCSPSigning, codeSigning
subjectKeyIdentifier   = hash 

[ server_distinguished_name ]
#c=GB
#o=SSS
#cn=mqweb
  • See above for the distinguished_name value.
  • req_extensions says use the section [server_req_extensions]

The ca2.config file used to sign it has

HOME            = .
RANDFILE        = $ENV::HOME/.rnd

####################################################################
[ ca ]
default_ca    = CA_default      # The default ca section
####################################################################
[ CA_default ]
default_days     = 1000         # How long to certify for
default_crl_days = 30           # How long before next CRL
#default_md       = sha1       # Use public key default MD
default_md       = sha256       # Use public key default MD
preserve         = no           # Keep passed DN ordering

x509_extensions = ca_extensions # The extensions to add to the cert

email_in_dn     = no            # Don't concat the email in the DN
copy_extensions = copy          # Required to copy SANs from CSR to cert

#defaults
base_dir      = .
certificate   = $base_dir/cacert.pem   # The CA certifcate
private_key   = $base_dir/cakey.pem    # The CA private key
new_certs_dir = $base_dir              # Location for new certs after signing
database      = $base_dir/index.txt    # Database index file
serial        = $base_dir/serial.txt   # The current serial number

unique_subject = no  # Set to 'no' to allow creation of
                     # several certificates with same subject.


####################################################################
[ ca_extensions ]

subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer:always
basicConstraints       = critical,CA:TRUE, pathlen:0
keyUsage               = nonRepudiation

####################################################################

[ signing_policy ]
countryName            = optional
stateOrProvinceName    = optional
localityName           = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied

[ clientServer ]

keyUsage               = digitalSignature, keyAgreement, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName         = DNS:localhost, IP:127.0.0.1, 
extendedKeyUsage       = serverAuth,clientAuth
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer:always
nsComment  = "clientserver"

The policy (my [ signing_policy] ) must have entries in it to create a valid Subject Distinguished name. Without it, I got a strange RACF code (0x0be8044d).

Send the CA to z/OS and import it

You need to send the CA public certificate to z/OS. This file looks like

-----BEGIN CERTIFICATE-----                                      
MIIFbTCCA1WgAwIBAgIUJw+gLLSFxqCyTdIyEUWyQ/g9JnEwDQYJKoZIhvcNAQEL.
...
-----END CERTIFICATE----- 

You can FTP the file, or create the file and use cut and paste. The file needs to be a sequential dataset with format VB. My file is VB, lrecl=256,blksize=6233. For the FTP I used

put docca256.pem ‘colin.docca256.pem’

You need to import this into RACF, and connect it to the keyrings.

//IBMRACF  JOB 1,MSGCLASS=H                                     
//S1  EXEC PGM=IKJEFT01,REGION=0M                               
//SYSPRINT DD SYSOUT=*                                          
//SYSTSPRT DD SYSOUT=*                                          
//SYSTSIN DD *                                                  
RACDCERT CHECKCERT('COLIN.DOCCA256.PEM') 
/*                       

The CHECKCERT gave me

Certificate 1:                                                          
                                                                        
  Start Date: 2022/10/09 11:45:43                                       
  End Date:   2025/10/08 11:45:43                                       
  Serial Number:                                                        
       >782A62948699FF3FB00238FB296E4A647B7DF07C<                       
  Issuer's Name:                                                        
       >CN=SSCA256.OU=CA.O=DOC.C=GB<                                    
  Subject's Name:                                                       
       >CN=SSCA256.OU=CA.O=DOC.C=GB<                                    
  Signing Algorithm: sha384ECDSA                                        
  Key Usage: HANDSHAKE, CERTSIGN                                        
  Key Type: NIST ECC                                                    
  Key Size: 256                                                         
                                                                        

Which matches what I expected, and gave me information about the certificate – ECC, 256, and signed with SHA 384 ECDSA, (from the -sha384 parameter above).

Define it to RACF and connect it to the user’s keyring

racdcert list  (label('Linux-CA256')) CERTAUTh 
RACDCERT DELETE  (LABEL('LINUXDOCCA256'))    CERTAUTH             
              
RACDCERT ADD('COLIN.DOCCA256.PEM') -                            
          CERTAUTH  WITHLABEL('LINUXDOCA256') TRUST 

RACDCERT ID(START1) CONNECT(RING(MQRING)-        
                            CERTAUTH     -       
                            LABEL('LINUXDOCCA256'))                     

If you delete a certificate, then it is removed from all keyrings. Once you have re-added it you need to reconnect it to all the keyrings. If you list the label (racdcert list (label(‘Linux-CA256’)) certauth) it will display where it is used, so you can read it.

Download the z/OS CA certificate

I downloaded the z/OS exported certificate in .pem format. it looks like

-----BEGIN CERTIFICATE-----                                       
MIIDYDCCAkigAwIBAgIBADANBgkqhkiG9w0BAQsFADAwMQ4wDAYDVQQKEwVDT0xJ  
... 
+9TRng==                                                          
-----END CERTIFICATE-----

You can use ftp or cut and paste. I created doczosca.pem.

Use it!

Before I could use it, I had to set up the server’s certificate, and download the z/OS CA certificate

I set up a bash script scl2.sh

name=docecadcd
host="-connect 10.1.1.2:4000"
CA="--CAfile doczosca.pem "

openssl s_client $host  $CA -cert $name.pem -certform PEM -key $name.key.pem -keyform PEM
 

Programming using AT-TLS

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 certificate”. I can’t tell where the 128 bit AEAD is in the short description.
    • 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.