Using PKI Server with the HTTPD web interface.

This post follows on from configuring PKI Server, and explains how to configure the HTTPD server, explains how to use it, and gives some hints on debugging it when it goes wrong.

Having tried to get this working (and fixing the odd bug) I feel that this area is not as well designed as it could have been, and I could not get parts of it to work.

For example

  • You cannot generate browser based certificate request because the <keygen> html tag was removed around 2017, and the web page fails. See here. You can use 1-Year PKI Generated Key Certificate instead, so not a big problem now we know.
  • The TLS cipher specs did not have the cipher specs I was using.
  • I was expecting a simple URL like https://10.1.1.2/PKIServer/Admin. You have to use https://10.1.1.2/PKIServ/ssl-cgi-bin/camain.rexx, which exposes the structure of the files. You can go directly go to the Admin URL using https://10.1.1.2/PKIServ/ssl-cgi-bin/auth/admmain.rexx, which is not very elegant.
  • For an end user to request a certificate you have to use https://10.1.1.2/Customers/ssl-cgi-bin/camain.rexx.
  • There seem to be few security checks.
    • I managed to get into the administrative panels and display information using a certificate mapping to a z/OS userid, and with no authority!
    • There are no authority checks for people requesting a certificate. This may not be an exposure as the person giving the OK should be checking the request.
    • There were no security checks for administration functions. (It is easy to add them(
  • You can configure HTTPD to use certificates for authentication and fall back to userid and password.
  • There is no FallbackResource specified. This is a default page which is displayed if you get the URL wrong.
  • The web pages are generated dynamically. These feel over engineered. There was a problem with one of the supplied pages, but after some time trying to resolve the problem, I gave up.

I’ll discuss how to use the web interface, then I’ll cover the improvements I made to make the HTTP configuration files meet my requirements, and give some guidance on debugging.

You may want to use a HTTPD server just for PKI Server, or if you want to share, then I suggest you allocate a TLS port just for PKI Server.

URL

The URL looks like

https://10.1.1.2:443/PKIServ/ssl-cgi-bin/camain.rexx

where (see Overview of port usage below for more explanation)

  • 10.1.1.2 is the address of my server
  • port 443 is for TLS with userid and password authentication
  • PKIServ is the part of the configuration. If you have multiple CA’s this will be CA dependant.
  • ssl-cgi-bin is the “directory” where …
  • camain.rexx the Rexx program that does the work.

With https:10.1.1.2:443/Customers/ssl-cgi-bin/camain.rexx this uses the same camain.rexx as for PKIServ, but in the template for displaying data, it uses a section with the same name (Customers) as the URL.

Overview of port usage

There are three default ports set up in the HTTPD server for PKI Server. I found the port set-up confusing, and not well document. I’ve learned (by trial and error) that

  • port 80 (the default for non https requests) for unauthenticated requests, with no TLS session protection. All data flows as clear text. You many not want to use port 80.
  • port 443 (the default for https requests) for authentication with userid and password, with TLS session protection
  • port 1443 for certificate authentication, with TLS Session protection. Using https://10.1.1.2:443/PKIServ/clientauth-cgi/auth/admmain.rexx, internally this gets mapped to https://10.1.1.2:1443/PKIServ/clientauth-cgi-bin/auth/admmain.rexx. I cannot see the need for this port and its configuration.

and for the default configuration

  • port:/PKIServ/xxx is for administrators
  • port:/Customers/xxx is for an end user.

and xxx is

  • clientauth-cgi. This uses TLS for session encryption. Port 1443 runs with user SAFRunAs PKISERV. All updates are done using the PKISERVD userid, this means you do not need to set up the admin authority for each userid. There is no security checking enabled. I was able to process certificates from a userid with no authority!
  • ssl-cgi-bin. This uses port TLS and 443. I had to change the file to be SAFRunAs %%CERTIF%% as $$CLIENT$$ is invalid. You have to give each administrator ID access to the appropriate security profiles.
  • public-cgi. This is used by some insecure requests, such as print a certificate.

I think the only one you should use is ssl-cgi-bin.

Accessing the services

You can start using

These both give a page with

  • Administration Page. This may prompt for your userid and password, and gives you a page
  • Customer’s Home Page. This gives a page https://10.1.1.2/Customers/ssl-cgi-bin/camain.rexx? called PKI Services Certificate Generation Application. This has functions like
    • Request a new certificate using a model
    • Pickup a previously requested certificate
    • Renew or revoke a previously issued browser certificate

Note: You cannot use https://10.1.1.2:1443/PKIServ/ssl-cgi-bin/camain.rexx, as 1443 is not configured for this. I could access the admin panel directly using https://10.1.1.2:1443/PKIServ/ssl-cgi-bin/auth/admmain.rexx

I changed the 443 definition to support client and password authentication by using

  • SSLClientAuth Optional . This will cause the end user to use a certificate if one is available.
  • SAFRunAs %%CERTIF%% . This says use the Certificate authentication when available, if not prompt for userid and password.

Certificate requests

I was able to use the admin interface and display all certificate requests.

Request a new certificate using a model.

I tried to use the model “1 Year PKI SSL Browser Certificate“. This asks the browser to generate a private/public key (rather than the PKIServer generating them). This had a few problems. Within the page is a <KEYGEN> tag which is not supported in most browsers. It gave me

  • The field “Select a key size” does not have anything to select, or type.
  • Clicking submit request gave me IKYI003I PKI Services CGI error in careq.rexx: PublicKey is a required field. Please use back button to try again or report the problem to admin person to

I was able to use a “1 Year PKI Generated Key Certificate

The values PKIServ and Customer are hard-coded within some of the files.

If you want to use more than one CA, read z/OS PKI Services: Quick Set-up for Multiple CAs. Use this book if you want to change “PKIServ” and “Customer”.

Colin’s HTTPD configuration files.

Because I had problems with getting the supplied files to work, I found it easier to restructure, parameterise and extend the provided files.

I’ve put these files up to github.

Basic restructure

I restructured and parametrised the files. The new files are

  • pki.conf. You edit this to define your variables.
  • 80.conf contains the definitions for a general end user, not using TLS. So the session is not encrypted. Not recommended.
  • 443.conf the definitions for the TLS port. You should not need to edit this while you are getting started. If you want to use multiple Certificate Authorities, then you need to duplicate some sections, and add definitions to the pki.conf file. See here.
  • 1443.conf the definitions for the TLS port for the client-auth path. You should not need to edit this while you are getting started. If you want to use multiple Certificate Authorities, then you need to duplicate some sections, and add definitions to the pki.conf file. See here.
  • Include conf/pkisetenv.conf to set some environment variables.
  • pkissl.conf. The SSL definitions have been moved to this file, and it has an updated list of cipher specs.

The top level configuration file pki.conf

The top level file is pki.conf. It has several sections

system wide

# define system wide stuff
# define my host name

Define sdn 10.1.1.2
Define PKIAppRoot /usr/lpp/pkiserv
Define PKIKeyRing START1/MQRING
Define PKILOG “/u/mqweb3/conf”

# The following is the default
#Define PKISAFAPPL “OMVSAPPL”
Define PKISAFAPPL “ZZZ”
Define serverCert “SERVEREC”
Define pkidir “/usr/lpp/pkiserv”

#the format of the trace entry
Define elf “[%{u}t] %E: %M”

Defined the CA specific stuff

# This defines the path of PKIServ or Customers as part of the URL
# This is used in a regular expression to map URLs to executables.
Define CA1 PKIServ|Customers
Define CA1PATH “_PKISERV_CONFIG_PATH_PKIServ /etc/pkiserv”

#Define the port for TLS
Define CA1Port 443

# specify the groups which can use the admin facility
Define CA1AdminAuth ” Require saf-group SYS1 “

other stuff

LogLevel debug
ErrorLog “${PKILOG}/zzzz.log”
ErrorLogFormat “${elf}”
# uncomment these if you want the traces
#Define _PKISERV_CMP_TRACE 0xff
#Define _PKISERV_CMP_TRACE_FILE /tmp/pkicmp.%.trc
#Define _PKISERV_EST_TRACE 0xff
#Define _PKISERV_EST_TRACE_FILE /tmp/pkiest.%.trc

#Include the files
Include conf/80.conf
Include conf/1443.conf
Include conf/443.conf

The TLS configuration file

The file 443.conf has several parts. It uses the parametrised values above, for example ${pkidir} is substituted with /usr/lpp/pkiserv/. When getting started you should not need to edit this file.

Listen ${CA1Port}
<VirtualHost *:${CA1Port}>

#define the log file for this port
ErrorLog “${PKILOG}/z${CA1Port}.log


DocumentRoot “${pkidr}”
LogLevel Warn
ErrorLogFormat “${elf}”

Include conf/pkisetenv.conf
Include conf/pkissl.conf
KeyFile /saf ${PKIKeyRing}
SSLClientAuth Optional
#SSLClientAuth None

RewriteEngine On

# display a default page if there are problems
# I created it in ${PKIAppRoot}/PKIServ,
# (/usr/lpp/pkiserv/PKIServ/index.html)
FallbackResource “index.html”

Below the definitions for one CA are defined. If you want a second CA, then duplicate the definitions,and change CA1 to CA2.

Notes on following section.

# Start of definitions for a CA

<IfDefine CA1>
SetEnv ${CA1PATH}
RewriteRule ¬/(${CA1})/ssl-cgi/(.) https://${sdn}/$1/ssl-cgi-bin/$2 [R,NE]

RewriteRule ¬/(${CA1})/clientauth-cgi/(.) https://${sdn}:1443/$1/clientauth-cgi-bin/$2 [R,NE,L]
ScriptAliasMatch ¬/(${CA1})/adm(.).rexx(.) “${PKIAppRoot}/PKIServ/ssl-cgi-bin/auth/adm$2.rexx$3
ScriptAliasMatch ¬/(${CA1})/Admin “${PKIAppRoot}/PKIServ/ssl-cgi-bin/auth/admmain.rexx”
ScriptAliasMatch ¬/(${CA1})/EU “${PKIAppRoot}/PKIServ/ssl-cgi-bin/camain.rexx”
ScriptAliasMatch ¬/(${CA1})/(public-cgi|ssl-cgi-bin)/(.*) “${PKIAppRoot}/PKIServ/$2/$3”
<LocationMatch “¬/(${CA1})/clientauth-cgi-bin/auth/pkicmp”>
CharsetOptions NoTranslateRequestBodies
</LocationMatch>
<LocationMatch “¬/(${CA1})/ssl-cgi-bin(/(auth|surrogateauth))?/cagetcert.rexx”>
Charsetoptions TranslateAllMimeTypes
</LocationMatch>
<IfDefine>

#End of definitions for CA1

Grouping the statements for a CA in one place means it is very easy to change it to use multiple CA’s, just repeat the section between <IfDefine…> and</IfDefine> and change CA1 to CA2.

The third part has definitions for controlling access to a directory. I added more some security information, and changed $$CLIENT$$ to %%CLIENT%%. This is a subset of the file, for illustration

# The User will be prompted to enter a RACF User ID
#and password and will use the same RACF User ID
# and password to access files in this directory
<Directory ${PKIAppRoot}/PKIServ/ssl-cgi-bin/auth>
AuthName AuthenticatedUser
AuthType Basic
AuthBasicProvider saf
Require valid-user

#Users must have access to the SAF APPLID to work
# ZZZ in my case
# it defaults to OMVSAPPL
<IfDefine PKISAFAPPL>
SAFAPPLID ${PKISAFAPPL}
</IfDefine>

# IBM Provided has $$CLIENT$$ where it should have %%CLIENT%%
# SAFRunAs $$CLIENT$$
# The following says use certificate if available else prompt for
# userid and password
SAFRunAs %%CERTIF%%
</Directory>…

Debugging hints and tips

I spent a lot of time investigating problems, and getting the definitions right.

Whenever I made a change, I used

s COLWEB,action=’restart’

to cause the running instance of HTTPD server to stop and restart. Any errors in the configuration are reported in the job which has the action=’restart’. It is easy to overlook configuration problems, and then spend time wondering why your change has not been picked up.

I edited the envvars file, and added code to rename and delete logs. For example rm z443.log.save, and mv z443.log z443.log.save .

I found it useful to have

<VirtualHost *:443>
DocumentRoot “${pkidr}”
ErrorLog “${PKILOG}/z443.log
ErrorLogFormat “${elf}”
LogLevel Warn


Where

  • Error logs is where the logs for this virtual host (port 443) are stored. I like to have one per port.
  • The format is defined in the variable Define elf “[%{c}t] %E: %M” in the pki.conf file. The c is compact time (2021-11-27 17:19:09). If you use %{cu}t you also get microseconds. I could not find where you just get the time, and no date.
  • LogLevel Warn. When trying to debug the RewriteRule and ScriptAlias I used LogLevel trace6. I also used LogLevel Debug authz_core_module:Trace6 which sets the default to Debug, but the authorization checking to Trace6.

With LogLevel Debug, I got a lot of good TLS diagnostics

Validating ciphers for server: S0W1, port: 443
No ciphers enabled for SSLV2
SSL0320I: Using SSLv3,TLSv1.0,TLSv1.1,TLSv1.2,TLSv1.3 Cipher: TLS_RSA_WITH_AES_128_GCM_SHA256(9C)

TLSv10 disabled, not setting ciphers
TLSv11 disabled, not setting ciphers
TLSv13 disabled, not setting ciphers
env_init entry (generation 2)
VirtualHost S0W1:443 is the default and only vhost

Then for each web session

Cert Body Len: 872
Serial Number: 02:63
Distinguished name CN=secp256r1,O=cpwebuser,C=GB
Country: GB
Organization: cpwebuser
Common Name: secp256r1
Issuer’s Distinguished Name: CN=SSCA256,OU=CA,O=SSS,C=GB
Issuer’s Country: GB
Issuer’s Organization: SSS
Issuer’s Organization Unit: CA
Issuer’s Common Name: SSCA256
[500865c0f0] SSL2002I: Session ID: A…AAE= (new)
[500865c0f0] [33620012] Peer certificate: DN [CN=secp256r1,O=cpwebuser,C=GB], SN [02:63], Issuer [CN=SSCA256,OU=CA,O=SSS,C=GB]

With LogLevel Trace6 I got information about the RewriteRule, for example we can see /Customers/EU was mapped to /usr/lpp/pkiserv/PKIServ/ssl-cgi-bin/camain.rexx

applying pattern ‘¬/(PKIServ|Customers)/clientauth-cgi/(.*)’ to uri ‘/Customers/EU’

AH01626: authorization result of Require all granted: granted
AH01626: authorization result of : granted

should_translate_request: r->handler=cgi-script r->uri=/Customers/EU r->filename=/usr/lpp/pkiserv/PKIServ/ssl-cgi-bin/camain.rexx dcpath=/

uri: /Customers/EU file: /usr/lpp/pkiserv/PKIServ/ssl-cgi-bin/camain.rexx method: 0 imt: (unknown) flags: 00 IBM-1047->ISO8859-1

# and the output

Headers from script ‘camain.rexx’:
Status: 200 OK
Status line from script ‘camain.rexx’: 200 OK
Content-Type: text/html
X-Frame-Options: SAMEORIGIN
Cache-Control: no-store, no-cache

PKI Server error messages

AH00526: … \xac

I got AH00526:

Syntax error on line … of…: RewriteRule: bad argument line ‘\xac/…

This was because I had ^ and I needed ¬ in the documents.

The USS command chtag -p /u/mqweb3/conf/443.conf gave me

untagged T=off /u/mqweb3/conf/443.conf

My 3270 emulator code page was Bracket CP 037 modified.

Using the ISPF edit command hex on, showed the correct hex data is x’5f’. This can display as ¬ or as ^ depending on your 3270 emulator.

IKYC901I Error 76677164 initializing ICL: The CA certificate in the ICL does not match the one in the keyring

I got this when I redefined my userids and keyrings. I recreated the VSAM files. Display the ICL VSAM file

export PATH=/usr/lpp/pkiserv/bin/
export LIBPATH=/usr/lpp/pkiserv/lib
export NLSPATH=/usr/lpp/pkiserv/lib//usr/lpp/nls/msg/%L/%N
/usr/lpp/pkiserv/bin/iclview -d \’PKISRVD.VSAM.ICL\’

Need to escape the data set name.

My file was empty, so I recreated the VSAM data sets.

IKYP022I Unable to register PKI Services for restart: Error 12, Reason 0x160

This server has not been set up for ARM (Automatic Restart Management). The return code makes no sense to me.

Ignore it.

IKYC009I LDAP post unsuccessful for object id = 101, state = 0x2150000, status =
581500960: No such object
IKYP039E DIRECTORY POST UNSUCCESSFUL. ERROR CODE = 581500960

Unable to get the CA DN from the LDAP server. Check the suffix (eg CN=PKICA,OU=SSS,O=ZZUR COMPANY

IKYP040I PKI SERVICES DOES NOT HAVE KEY GENERATION CAPABILITY

You are missing the TokenName such as

[SAF]
KeyRing=PKISRVD/CARING
TokenName=PKISRVD.PKIToken

in /etc/pkiserv/pkiserv.conf.

This in turn caused rc 0 safrc 8 racfrc 8 racfrs 64 with function GENCERT in IRRSPX00 (R_pkiserve).

In the PKISERVD log (with debug trace turned on)

POLICY IKYK001I Unexpected PKCS#11 icsfpkcs11::genKeyPair return code 0x190. The request is not processed.

CORE IKYC010I Error 791740499 returned from CP_NewKeysCreate: Unable to generate or store a public/private key pair through ICSF

CORE IKYC010I Error 791740499 returned from JNH_create_certificate: Unable to generate or store a public/private key pair through ICSF

httpd: SSL0222W: SSL Handshake Failed, No ciphers specified (no shared
ciphers or no shared protocols).

During a TLS handshake there was no matching certificate found for the client.

I added

SSLCipherSpec TLS_AES_128_GCM_SHA256
SSLCipherSpec TLS_AES_256_GCM_SHA384
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
SSLCipherSpec TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
SSLCipherSpec TLS_RSA_WITH_AES_128_GCM_SHA256
SSLCipherSpec TLS_RSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_RSA_WITH_AES_128_CBC_SHA
SSLCipherSpec TLS_RSA_WITH_AES_256_CBC_SHA
SSLCipherSpec TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

ICH408I USER(…) GROUP(…) NAME(ADCDA )NOT AUTHORIZED TO ADMINISTER DIGITAL CERTIFICATES OR CERTIFICATE REQUESTS. READ DENIED

and

IKYI002I SAF Service IRRSPX00 Returned SAF RC = 8 RACF RC = 8 RACF RSN = 8 Request denied, not authorized.

The user issuing the request was not authorised to IRR.RPKISERV.PKIADMIN CLASS(FACILITY).

Note what the message says

  • READ DENIED
  • UPDATE DENIED

Use

tso rlist facility irr.RPKISERV.PKIADmin auth

and connect the userid ( if required) to a group or give the required access with

PERMIT IRR.RPKISERV.PKIADMIN CLASS(FACILITY)
ID(ADCDA ) ACCESS(read )

setropts raclist(FACILITY) refresh

(163) EDC5163I SAF/RACF
extract error. (errno2 = 0x0BE8081C ): …


pthread_security_applid_np(__CREATE_SECURITY_ENV,
__CERTIFICATE_IDENTITY, 32, …, NULL, 0,… returned -1,
errno 163 errno2 be8081c 0x0be8081

The userid being used is revoked

IEW2646W 5383 ESD RMODE(24) CONFLICTS WITH USER-SPECIFIED RMODE(ANY) FOR SECTION …. CLASS B_TEXT.
IEW2646W 5383 ESD RMODE(24) CONFLICTS WITH USER-SPECIFIED RMODE(ANY) FOR SECTION … CLASS B_LIT.

I got these trying to bind a C program. I also had an assembler stub which caused this problem.

I added the RMODE and AMODE to my assembler program and cured the problem

CALLPRTF RMODE ANY
CALLPRTF AMODE ANY

CALLPRTF CSECT

HTTPD configuring the config file

The HTTPD server has a configuration file. This has some great facilities for making it easy to manage the contents of the configuration file. In summary these are

  • Ability to include files into the configuration
  • Define and use symbols
  • If then..
  • Use of regular expressions

Use include files

You can use Include

  • Include /usr/colin/a.conf an explicit, fully qualified path.
  • Include conf/ssl.conf relative to the ServerRoot directory (which you specify at start up).
  • Include conf/vhosts/*.conf multiple files in the directory.

If the files do not exist, the server will fail to start.

You can also use IncludeOptional if the files do not exist, the processing continues

Define and use symbols

You can use Define

  • Define symbol
  • Define symbol myvalue

Use the symbol

Define myvalue “mydata”

DocumentRoot “/var/%{myvalue}”

If symbol has been Defined

You can check to see if a symbol has been defined

<IfDefine>symbol>

</IfDefine>

<IfDefine !symbol>

</IfDefine>

Use a symbol in an if

<If %{v} == “yes”>

</if>

If processing

You can use

  • <IfDefined.. to determine if a symbol has been defined
  • <If to use value
  • <IfDirective. Check to see if the directive was specified in a -D parameter at startup.

You can do

<If … >

</If>

<ElseIf…>

</Elseif>
<Else>

</Else>

You can do

<If ..>
Error ” message “
</If>

This produces

AH00526: Syntax error on line … of /u/mqweb3/conf…

and ends. It does not continue past this.

Regular expressions

Within many of the statements you can use regular expressions, see Apache Documentation and Wikipedia. For example, all .gif and .jpg files under any “images” directory could be written as “/images/.*(jpg|gif)$“.

Packaging files

If you have

Listen 1834
<VirtualHost *:1834>

</VirtualHost>
<VirtualHost *:1834>

</VirtualHost>

The second <VirtualHost is ignored.

Multiple <Location..> for the same resource name do not work.

Try to put common definitions in one file and include them where needed, for example the list of TLS certificates.

If the same value is used in multiple configuration files, make it a variable and set it once. If it is changed, it will be picked up automatically. You can display the configuration to see what is actually being used.

HTTPD, SAFAPPL and protecting web resources

The HTTPD server can check a userid’s access to a RACF APPLID to enforce checks on resources.

Setting it up to give access seemed trivial, setting it up to deny access took longer.

In my VirtualHost I had

SAFAPPLID ZZZ
AuthType Basic
AuthBasicProvider saf
SAFRunAs %%CLIENT%%
Require saf-user ADCDA
Require saf-group SYS1

This says

  • userids must have read access to the APPL profile ZZZ.
  • a request should include the userid and password as part of the request.
  • the userid must be ADCDA or in group SYS1.

If the RACF profile is not set up (or not set up properly) then access defaults to yes.

Setup the profile

rdefine APPL ZZZ uacc(NONE) NOTIFY(COLIN)
setropts raclist(APPL) refresh

The NOTIFY is to notify a user(COLIN) when a user is denied access to the resource. This is useful while testing to check authentication is working. A failed attempt gave me

ICH70004I USER(ADCDB) GROUP(ADCDGR) NAME(COLIN PAICE) ATTEMPTED ‘READ’ ACCESS OF ‘ZZZ’

You do not get a message if a user does not have the right access (as you do with other resources), so the NOTIFY seems the only way of finding out there is a problem.

If I logged on with certificate, the same checks were done.

To give a user access, (actually it is better to give the user’s group access)

permit ZZZ class(APPL) ID(WEB2) access(READ)
setropts raclist(APPL) refresh

Problems with SAFAPPLID

The SAFAPPLID statement is meant to be supported in directory, virtual host, and server sections, but it only accepted it in the <Directory… section.

For example the following fails to parse

<virtualHost *:8833>
SAFAPPLID ZZZ

with

AH00526: Syntax error on line 11 of /u/mqweb3/conf/notls.conf: SAFAPPLID not allowed here

Originally I defined APPL ZZZZZZZZ, but used ZZZZZZZ (7 Z’z not 8). And the application continued to have access to HTTPD. By specifying NOTIFY(COLIN) this notified me when the request failed.

With

<VirtualHost …>
LogLevel debug
ErrorLog “/u/mqweb3/conf/yy.log”

I got the following in the yy.log file

pthread_security_applid_np(__CREATE_SECURITY_ENV, __USERID_IDENTITY, 5, colin, …, 0, ZZZ) returned OK

From this I can see the userid “colin”, the SAFAPPLID “ZZZ”, and the return code “OK”.

Getting HTTPD server to work with TLS on z/OS

This is one of a series of blog posts on HTTPD.

The HTTPD web server is the Apache web server ported to run on z/OS. It runs in Unix Services, and behaves like a proper z/OS program, for example it can use z/OS userids and keyrings.

One catch is that there is Apache SSL, and IBM SSL. For example SSLProtocolEnable is part of IBM SSL support, and does not exist in Apache SSL; and SSLVerifyClient exists in Apache SSL, and not in IBM SSL support.

You need to know which options you need to use. With the wrong options you will get a message like

AH00526: Syntax error on line .. of … Invalid command … , perhaps misspelled or defined by a module not included in the server configuration

This post describes how to get TLS to work with the HTTPD server.

You can use TLS to encrypt the session data, and you can use TLS to use the client certificate as authentication.

The IBM httpd TLS options are described here.

Before you start

  • You need a keyring with certificates. I won’t cover this, as it well documented. I had problems using elliptic keys with size other than 256 and 384. See here.
  • You need to select a port for the TLS sessions. The default for TLS is 443. You may wish to use another port for isolation, and ease of management and configuration.
  • I set up a self contained Virtual Host for the TLS stuff, you do not need to do this.
  • Consider putting your common TLS definitions in a file and including it where needed. For example the list of TLS Ciphers, and the keyring. If you want to change the parameters, you change it once, and restart the server.
  • You can define the SSL parameters in the main server section of the configuration, or within a virtual host. A definition within a virtual host overrides the main server definitions.

Establish a TLS session to encrypt the session data.

Set up permissions

The started task userid needs access to read the keyring. Because WEB2 is not the owner of the ring (START1 is the owner), WEB2 needs CONTROL to get access to the private key.

permit START1.MQRING.LST class(RDATALIB) ID(WEB2) ACCESS(CONTROL)
setropts raclist(RDATALIB) refresh

PERMIT CSFOWH CLASS(CSFSERV) ID(WEB2) ACCESS(READ)
SETROPTS RACLIST(CSFSERV) REFRESH

I was using keyring START1.MQRING,and the httpd server userid is WEB2.

Setup the configuration

For the z/OS SSL support you need

LoadModule ibm_ssl_module modules/mod_ibm_ssl.so

The Apache module mod_ssl.so does not exist on z/OS, so it, and the facilities it provides cannot be used.

I set up a configuration file tls.conf (and used Include conf/tls.conf in my colin.conf)

Listen 8832
<VirtualHost *:8832>
SSLEnable

# SSLTrace
<Location /xxxx.html>

</Location>

ErrorLog “/u/mqweb3/conf/tls.log”
ErrorLogFormat “[%{%X}t]![%l] %F: %E: [client %a] %M”

KeyFile /saf START1/MQRING#

# SSLVerifyClient None

SSLClientAuth optional

SSLProtocolEnable TLSv12
SSLServerCert SERVEREC
SSLCipherSpec TLS_AES_128_GCM_SHA256
SSLCipherSpec TLS_AES_256_GCM_SHA384

# TLS 1.3 cipher specs
# SSLCipherSpec TLS_CHACHA20_POLY1305_SHA256
# SSLCipherSpec TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
# SSLCipherSpec TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
SSLCipherSpec TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
SSLCipherSpec TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
SSLCipherSpec TLS_RSA_WITH_AES_128_GCM_SHA256
SSLCipherSpec TLS_RSA_WITH_AES_256_GCM_SHA384
SSLCipherSpec TLS_RSA_WITH_AES_128_CBC_SHA
SSLCipherSpec TLS_RSA_WITH_AES_256_CBC_SHA
SSLCipherSpec TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
SSLClientAuth none
TLS13Options ServerKeyRefresh=604800

</VirtualHost>

Debugging it

You can either have an SSL VirtualHost wide trace, or a server wide SSL trace (or both)

VirtualHost wide trace

You can use SSLTrace (see above) in your virtual host and it writes it to the error log for that virtual host.

Server wide trace

I changed the environment to create a GSK trace. I added

export GSK_TRACE=0x0f
export GSK_TRACE_FILE=/u/mqweb3/conf/httpd.gsktrace

to /usr/lpp/ihsa_zos/bin/envvars .

You use the gskit command gsktrace /u/mqweb3/conf/httpd.gsktrace > gsk_out to format it.

Testing it

If the httpd server starts, try connecting to it from a web browser. Don’t for get to use https:

https://10.1.1.2:8832

If you get

Internal Server Error

This means the TLS handshake worked, you just have not set up the backend application.

When I used

https://10.1.1.2:8832/xxxx.html

(matching the entry in my VirtualHost definition) I was prompted for userid and password.

Using a client certificate for authentication and identification.

You can use a certificate on a client to authenticate with the server, without having to enter a userid and password. The server needs the CA from the client to be able to authenticate the client certificate. I had problems using elliptic keys on the client with size other than 256 and 384. See here.
You need to set up the certificate in RACF to map from the certificate to a userid.

Map from certificate to userid

My certificate had DN CN=secp256r1,O=cpwebuser,C=GB.

I used the following to map it to userid ADCDA

RACDCERT LISTMAP ID(ADCDA)
RACDCERT DELMAP(LABEL(‘SECP256R1 )) ID(ADCDA)
RACDCERT MAP ID(ADCDA ) –
SDNFILTER(‘CN=secp256r1.O=cpwebuser.C=GB’) –
WITHLABEL(‘SECP256r1’)
RACDCERT LISTMAP ID(ADCDA)
SETROPTS RACLIST(DIGTNMAP, DIGTCRIT) REFRESH

Note: In the certificate the DN is CN=secp256r1,O=cpwebuser, in the RACF command, the comma is replace with a period CN=secp256r1.O… I get it wrong every time!

To force the client to send a certificate you need

SSLClientAuth Required

instead of SSLClientAuth none in your <VirtualHost>…</VirtualHost>.

You also need to specify SAFRunAs

<Location /xxxx.html>
AuthName colinvh
AuthType Basic
AuthBasicProvider saf
SAFRunAs %%CERTIF_REQ%%
Require saf-user ADCDA
Require saf-user COLIN
</Location

The documentation said SAFRunAs can be in “directory, virtual host, server config” , I could only get it to be accepted in the location or the directory statement.

My certificate mapped to ADCDA userid, and so with this certificate I can display page xxxx.html.

SAFAPPLID didn’t work at first.

You can use SAFAPPLID you can say that a user needs access to a profile in the APPL class, for example PAYROLL. The default is OMVSAPPL.

Initially I could not get SAFAPPLID to work. This was due to a set up error. See here for more information.

AuthName didn’t work

When HTTPD prompts you for a userid and password, it is meant to display the authname as the title of the popup window, so you know which userid and password to specify. It didn’t display it for me. I could tell from the network traffic that the AuthName was sent down to the Chromium Browser as WWW-Authenticate: Basic realm=”colinvh“. I believe this is for integrity reasons – someone could change the value, and get you to enter “the wrong” credentials.

Now you’ve go it to work

You should consider

  • How many ports do you need to support.
  • Moving your TLS definitions into one configuration file, and include this where needed.
  • Removing the weak SSL cipher specs.
  • Moving to TLS 1.2 or above.

Documentation

HTTPD server – what is configured?

This is one of a series of blog posts on HTTPD.

You can run an Apache command, or display in the web browser, the active configuration with variables substituted, and If statements processed.

The server can dump configuration at startup. The best option was

-t -DDUMP_CONFIG

which displays the configuration with symbols substituted and after If processing. You can use this as part of your startup, or standalone, or as a web page. The web page displays more information.

/usr/lpp/ihs*/bin/apachectl -f /u/mqweb3/conf/httpd.conf -t -DDUMP_CONFIG > aa

This displays lots of good information. Below is an illustrative subset of what is available

  • Server settings
    • Server build:Jun 10 2020 16:22:51
    • Host Name/port:10.1.1.2:8831
    • Server Root: /u/mqweb3
  • Information about each module, for example ‘Module Name:mod_rewrite.c
    • Module directives, a list of all the actions supported eg
      • ‘RewriteRule – an URL-applied regexp-pattern and a substitution URL
    • Current configuration for that module, with If statements processed, and variables substituted
      • In file: /u/mqweb3/conf/80.conf
  • Information about each module, for example ‘Module Name:mod_ibm_ssl.c
    • Module directives, a list of all the actions supported eg
      • ‘SSLEnable – SSL is enabled for this server
    • Current configuration for that module, with If statements processed, and variables substituted. Line 10 was ‘Keyfile /saf ${pkiRing}’
      • In file: /u/mqweb3/conf/pkissl.conf   
      • 1:   SSLEnable  
      • 10:   Keyfile /saf START1/MQRING
      • etc

To run it as a web page, the configuration is below.

LoadModule info_module modules/mod_info.so
<Location /server-info>
SetHandler server-info
AuthType Basic
AuthBasicProvider saf
SAFRunAs PKISERV
</Location>

and the URL was

http://10.1.1.2:8831/server-info

Extending the HTTPD server on z/OS

The HTTPD web server is the Apache web server ported to run on z/OS. It runs in Unix Services, and behaves like a proper z/OS program, for example it can use z/OS userids and keyrings.

The configuration is easy, it is text driven (rather than XML), can imbed other configuration files, and can substitute variables.

I found the Apache documentation was as good, but the z/OS documentation was not very good. I prefer baby steps, taking the smallest system and adding functions, rather than configure everything and be disappointed when fails to work.

This post follows on Getting started with httpd server on z/OS and describes how to configure your first web page. Other posts on HTTPD server

Baby step number 2 – extending it

My HTTPD instance directory is /u/mqweb3/ .

I like to keep any changes I make to a configuration file, in a different file, and include this file in the original file. This way, if the original file changes, I just have to add the include statement rather than “diff” the my config file with the new config file. I also like to logically group changes, so my TLS configuration are in the tls.conf file, my definitions for port 8800 are in a file 8800.conf.

In the /u/mqweb3/conf is the httpd.conf file.

I edited this, and inserted

Include conf/colin.conf

at the bottom of the file.

I created /u/mqweb3/conf/colin.conf with

LogLevel debug

ErrorLog “/u/mqweb3/conf/error.log”

LoadModule rewrite_module modules/mod_rewrite.so
LoadModule authnz_saf_module modules/mod_authnz_saf.so
LoadModule ibm_ssl_module modules/mod_ibm_ssl.so

<Location /server-status>
AuthName “Colins Page”
AuthType Basic
AuthBasicProvider saf
Require valid-user
AuthSAFExpiration “EXPIRED! oldpw/newpw/newpw”
AuthSAFReEnter “Enter new password one more time”
CharsetSourceEnc IBM-1047
CharsetDefault ISO8859-1
SetHandler server-status
</Location>

The LoadModules provide the SAF support for logon

Shutdown the server (use P HTTPPCP6) – do not just cancel it – as there are several address spaces running for the server.

Restart the server (or do S httpcp,action=’restart’ ), fix any problems and try logging on to

http://10.1.1.2:8300/server-status

This should prompt for userid and password, and display the status of the server. The web browsers remember the userid and password, so if you want to reuse the page it will not prompt you for the userid and password. To change to a different userid and password you will need to restart the browser.

While playing with pages and logging on, I found the curl request

curl -u colin:password -i http://10.1.1.2:8830/xxxx.html

a good way of checking the page out, and logging on each time, as the password is not saved.

If you get

BPXP015I HFS PROGRAM /usr/lpp/ihsa_zos/bin/httpd IS NOT MARKED PROGRAM
CONTROLLED.
BPXP014I ENVIRONMENT MUST BE CONTROLLED FOR SERVER (BPX.SERVER)
PROCESSING.

You need to use the command

extattr +p /usr/lpp/ihsa_zos/bin/httpd

Create your a virtual host (container)

The HTTPD server can support multiple ports, and treat them as isolated environments. These are known as Virtual Hosts.

In my colin.conf I add

Include conf/vhost8831.conf

I created a file vhost8831.conf

Listen 8831
<VirtualHost *.8831>

<Location /xxxx.html>

#ServerName Colins.com
AuthName colinvh
AuthType Basic
AuthBasicProvider saf
#Require valid-user

Require saf-user COLIN JOE

# CharsetSourceEnc IBM-1047
# CharsetDefault ISO8859-1
# SetHandler server-status
</Location>

<Directory “/u/mweb3/htdocs”>
Require saf-user COLIN JOE
# Require saf-group SYS1

DocumentRoot “/u/mqweb3/htdocs”
#DirectoryIndex index_ihs.html

</Directory>
ErrorLog “/u/mqweb3/conf/zz.log”
ErrorLogFormat “[%t] [%l] %F: %E: [client %a] %M”

</VirtualHost>

Only userids COLIN and JOE are authorised to this (http://10.1.1.2:8831/xxxx.html service).

Restart the server

If you are authorised to use this service you will get

The requested URL was not found on this server.

Because this has not been set up yet.

The DocumentRoot works with the URL to identify a file.

The URL

http://10.1.1.2:8831/xxxx.html

will look for xxxx.html in DocumentRoot so it looks for file /u/mqweb3/htdocs/xxxx.html .

Baby steps 3 – create a page.

Create a file /u/mqweb3/htdocs/xxxx.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4//EN"> 
<html lang="en"> 
<head> 
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> 
<title>Colin's page</title> 
</head> 
<body width="778" height="556" background="images/ihs/background.gif" resize="no" scroll="no"> 
<h1>Colins header</h1> 
<p>Colin</p> 
</body> 
</html> 

Retry the web browser page.

When this is displayed you get

Where

  • The title maps to the page heading
  • The background image comes from the body

Getting started with HTTPD server on z/OS

Apache web server

The HTTPD web server is the Apache web server ported to run on z/OS. It runs in Unix Services, and behaves like a proper z/OS program, for example it can use z/OS userids and keyrings. It starts in seconds!

The configuration is easy, it is text driven (rather than XML), can imbed other configuration files, and can substitute variables.

I found the Apache documentation was very good, but the z/OS documentation was not as good. I prefer baby steps, taking the smallest system and adding functions, rather than configure everything and be disappointed when it fails to work first time.

Other posts

Getting started

I used the IBM HTTP Server – Powered by Apache Version 9 PDF document. The text below is an addition to the IBM documentation, not a replacement. I’m trying to fill the holes in the documentation.

The product comes pre-installed. Mine was in /usr/lpp/ihsa_zos/bin/httpd

You need a directory for your HTTPD instance.

You need the userid to run the server.

ADDUSER WEB2 NAME(‘COLWEB’) NOPASSWORD –
OMVS(AUTOUID ASSIZE(256000000) THREADS(512) –
Program(‘/bin/sh’) home(‘/u/mqweb3/’))

CONNECT IBMUSER GROUP(COLWEB )
CONNECT WEB2 GROUP(COLWEB )

When you set up the userid, it is better to use OMVS(AUTOUID .. than to give a specific numeric id, similarly use OMVS(AUTOGID… for the group.

The userid needs OMVS, PROGRAM= /bin/sh, and a home directory.

Setup the system with environment file

The /usr/lpp/ihsa_zos/bin/envvars file allows you to set up an image wide environment file. You can rename /usr/lpp/ihsa_zos/bin/envvars-std to /usr/lpp/ihsa_zos/bin/envvars

I set up this file with

#/!bin/sh
IHS=/usr/lpp/ihsa_zos
LIBPATH=$IHS/lib:$IHS/modules:$IHS
PATH=$IHS/bin
_EDC_ADD_ERRNO2=1
_BPX_SHAREAS=NO
_BPX_BATCH_SPAWN=YES
GSK_SSL_HW_DETECT_MESSAGE=1
LC_ALL=En_US.IBM-1047

Create the instance

Follow the instructions in the documentation.

cd /usr/lpp/ihsa*
umask 022
bin/install_ihs /u/mqweb3 8300

Note down the port you specified (8300) as you will need it when you try to connect to the server.

The documentation says switch to the instance directory and issue apachectl -v. This failed for me because the path and libpath were not set up. I set up the envvars file (above) and it worked.

/usr/lpp/ihs*/bin/apachectl -v

When I ran it, it produced

test: /usr/lpp/ihsa_zos/bin/apachectl 49: FSUM7351 not found
[: /usr/lpp/ihsa_zos/bin/apachectl 74: FSUM7351 not found
[: /usr/lpp/ihsa_zos/bin/apachectl 87: FSUM7351 not found
[: /usr/lpp/ihsa_zos/bin/apachectl 92: FSUM7351 not found
Server version: IBM_HTTP_Server/9.0.5.5 (Unix) (SMP/E, 64-bit)
Server built: Jun 10 2020 16:22:51

The FSUM7351 messages are OK. For example the apachectl script has

if test -f /usr/lpp/ihsa_zos/bin/envvars; then
. /usr/lpp/ihsa_zos/bin/envvars
fi

This checks the file exists before invoking it.

The apachectl takes the following options, so you can display the configuration, or modify the start.

Options:
-D name : define a name for use in directives
-d directory : specify an initial ServerRoot
-f file : specify an alternate ServerConfigFile
-C “directive” : process directive before reading config files
-c “directive” : process directive after reading config files
-e level : show startup errors of level (see LogLevel)
-E file : log startup errors to file
-v : show version number
-V : show compile settings
-h : list available command line options (this page)
-l : list compiled in modules
-L : list available configuration directives
-t -D DUMP_VHOSTS : show parsed vhost settings
-t -D DUMP_RUN_CFG : show parsed run settings
-S : a synonym for -t -D DUMP_VHOSTS -D DUMP_RUN_CFG
-t -D DUMP_MODULES : show all loaded modules
-M : a synonym for -t -D DUMP_MODULES
-t -D DUMP_SSL_CONFIG: show parsed SSL vhost configurations
-t -D DUMP_SSL_CIPHERS: show all known SSL ciphers
-t -D DUMP_UNIFIED_CONFIG: show configuration with all includes merged
-t -D DUMP_INCLUDES: show all included configuration files
-t : run syntax check for config files
-T : start without DocumentRoot(s) check
-X : debug mode (only one worker, do not detach)

-t -DDUMP_CONFIG is very useful as it shows what you have configured after any <If…> and after variable substitution. I use this as a standalone command to see what’s configured.

Note:I had problems using //STDENV in the started task, so I had to use the envvars file.

Create the JCL procedure

See the documentation.

//HTTPCP PROC ACTION='start',
// DIR='/usr/lpp/ihsa_zos',
// CONF='/u/mqweb3/conf/httpd.conf'
//*---------------------------------------------------------
//IHS EXEC PGM=BPXBATCH,REGION=0M,
// PARM='SH &DIR/bin/apachectl -k &ACTION -f &CONF -DNO_DETACH ',
// MEMLIMIT=1236M
//STDOUT DD SYSOUT=H
//STDERR DD SYSOUT=H
// PEND

BPXBATCH needs REGION=0M, and at least MEMLIMIT=1236M

Define the STARTED task to RACF.

RDEFINE STARTED HTTPCP* STDATA(USER(WEBSRV)
SETROPTS RACLIST(STARTED ) REFRESH

Start the started task

S HTTPCP

You cannot just type P HTTPCP. In the syslog I had

CRIHS0001I IHS S0W1 is active. 83951827 0.0.0.0:8300 unspecified:-1.
Use jobname HTTPCP6 for console commands.

This means you have to issue P HTTPCP6 to stop it.

You can also use

s HTTPCP,action=’start’
s HTTPCP,action=’stop’

To start and stop the server.
Note: When you run these commands it checks the syntax of the configuration file, and if there is a problem, then the command is not executed. I kept wondering why my HTTPD instance was not shutting down; it was because I had a configuration error, and so the stop request was being ignored.

If it starts (there are no helpful messages saying success) try connecting a web browser to it in my case

http://10.1.1.2:8300

This gave me page with links to IBM sites. If you get here – you have done the first baby step. You cannot do much with the server.