README ZXID

Sampo Kellomäki (sampo@iki.fi)

ZXID.org Identity Management toolkit implements standalone SAML 2.0 and Liberty ID-WSF 2.0 stacks and aims at implementing all popular federation and ID Web Services protocols. It is a C implementation with minimal external dependencies - OpenSSL, CURL, and zlib - ensuring easy deployment (no DLLhell). Due to its small footprint and efficient and accurate schema driven implementation, it is suitable for embedded and high volume applications. Language bindings to all popular highlevel languages such as PHP, Perl, and Java, are provided via SWIG. ZXID implements, as of July 07, SP, WSC, and WSP roles. IdP role will follow as the project evolves. ZXID.org ist eine C-Bibliothek, die den vollständigen SAML 2.0-Stack implementiert und alle populären Identitätsverwaltungs-Protokolle wie Liberty ID-FF 1.2, WS-Federation, WS-Trust und ID-Webservices wie Liberty ID-WSF 1.1 und 2.0 implementieren will. Sie beruht auf Schema-basierter Code-Erzeugung, woraus eine genaue Implementation resultiert. SWIG wird verwendet, um Schnittstellen zu Skriptsprachen wie Perl, PHP und Python sowie zu Java bereitzustellen. Sie kann als SP, WSC und WSP fungieren. A biblioteca de gestão de identidades ZXID.org é uma implementação, em C, das normas SAML 2.0 e Liberty ID-WSF 2.0 com dependências externas mínimas - OpenSSL, CURL, e zlib - facilitando uma implantação fácil sem "inferno dos DLL". Sendo económica em consumo de recursos é indicada para aplicações embutidas ou de grande volume e performance. A biblioteca é disponibilizada para todos os linguagens de programação de alto nível como, p.ex., PHP, Perl, e Java, atravez de interfáces SWIG. ZXID de hoje (Jul 07) pode funcionar nos papeis SP (Provedor de Serviços), WSC (Cliente de Serviços Web) e WSP (Provedor de Serviços Web), sendo o papel IdP (Provedor de Identidade) suportado na futura evolução do projecto. La librería de gestión de identidades ZXID.org es una implementación en C de las normas SAML 2.0 y Liberty ID-WSF 2.0, con dependencias externas mínimas - OpenSSL, CURL, y zlib - que elimina el "Infierno DLL" en su implantación. Como ZXID es muy económica, es apta para aplicaciones embebidas o de gran volumen y envergadura. Los lenguajes de programación de alto nivel, como Perl, PHP, y Java, son soportados con generador de interfaces SWIG. Hoy (Feb 07) el ZXID soporta los roles SP (proveedor de servicios) y WSC (cliente de los servicios web). Los roles IdP (proveedor de identidades) y WSP (proveedor de servicios web) serán soportados en fases futuras del proyecto. ZXID.org on verkkohenkilöllisyyden ja -tunnisteiden hallintakirjasto joka tukee SAML 2.0 (sisäänkirjaantuminen) ja Liberty ID-WSF 2.0 (henkilöllisyyteen pohjautuvat webbipalvelut) standardeja. ZXID vaatii vain OpenSSL, CURL ja zlib kirjastot joten se välttää "DLL helvetti"-ongelman. Skemapohjaisena C toteutuksena se on tarkka ja taloudellinen ja kelpaa sulautettuihin ja erittäin kovaa suorituskykyä vaativiin sovelluksiin. Se tukee korkeantason kieliä - kuten Perliä, PHP:tä, ja Javaa - SWIG generoiduin rajapinnoin. ZXID tukee (Heinäkuu 07) SP (palveluntarjoaja), WSC (webbipalvelunkutsuja), ja WSP (webbipalveluntarjoaja) rooleja. IdP (henkilöllisyydenvarmentaja) rooli toteutetaan projektin tulevissa vaiheissa.

1 Who needs this?

ZXID project has currently (Jan 2007) five outputs

libzxid

A C library for supporting SAML 2.0, including federated Single Sign-On (SSO)

zxid

A C program that implements a SAML Service Provider (SP) as a CGI script

Net::SAML

A Perl module wrapping libzxid. Also zxid.pl, that implements SP in mod_perl environment, is supplied.

php_zxid

A PHP extension that wraps libzxid. Also supplied: zxid.php that implements SP in mod_php environment.

libzxidjni.so

A Java JNI extension that wraps libzxid. Also supplied: zxid.java that implements SP as a CGI script.

You need this if you are

Web Master

You want to enable SAML based Single Sign-On (SSO) to your web site. In this case you would use the zxid SP CGI script directly, only configuring it slightly or you can go the zxid_simple() route. Otherwise you can hint your PHP or perl developer that this functionality is available and your want it.

Perl Developer

You can use the Net::SAML module to integrate SSO to your application and web site. Given the direct perl support, this is easier than fully understanding the C interface. Both mod_perl and perl as CGI are supported.

PHP Developer

You can use dl("php_zxid.so") to load the module and access the high level functionality, such as SAML 2.0 SSO. We support functionality roughly equivalent to perl Net::SAML. The PHP module is fully ready to use for SSO, but we expect to add a lot more, such as WSC, in future. Both mod_php5 and php as CGI are supported. php4 should also work.

Java Developer

You can use System.loadLibrary("zxidjni") to pull into your Java proram the full power of the ZXID. The functionality supported is roughly equal to Net::SAML.

Web Developer

You want to integrate SAML based SSO to your web site tool or product so that your customers can enjoy SSO enabled web sites. In this case you would study zxid.c for examples and use libzxid.a to implement the functionality in your own program.

Identity Management hacker

You need some building blocks: you will study libzxid and add to it, contributing to the project.

ZXID Project has vastly more ambitious goals. See the ZXID Project chapter later in this document.

Conor Cahill of Intel (formerly AOL) said back in 2006:

IMNSHO, better go Liberty up front and have the confidence that you do not need to upgrade later - or run two parallel systems. The Liberty (or SAML 2.0) system is comprehensive and addresses every use case anyone has thought so far. The percieved complexity is really an implementation issue and not underlying propery of the spec. Since we provide an implementation, the "complexity" is not customer problem.

2 Installing

If you want to try ZXID out immediately, we recommend compiling the library and examples and installing one of the examples as a CGI script in an existing web server. See later chapters for more details.

  tar xvzf zxid-0.15.tgz
  cd zxid-0.15
  # N.B.  There is no configure script. The Makefile works for all
  #       supported platforms by provision of correct TARGET option.
  # N.B2: We distribute some generated files. If they are missing, you need
  #       to regenerate them: make cleaner; make dep ENA_GEN=1
  # Standard place is /var/zxid. You can change this with
  #   make ZXID_PATH=/usr/local/var
  make                   # default Linux. Do `make TARGET=sol8' for Solaris
  make dir               # Creates /var/zxid hierarchy (may need to be root)
  
  make samlmod           # optional
  make samlmod_install   # optional: install Net::SAML perl module
  make phpzxid           # optional
  make phpzxid_install   # optional: install php_zxid.so PHP extension
  make javazxid          # optional

  cp zxid <webroot>/
  # configure your web server to recognize zxid a CGI, e.g.
  mini_httpd -p 8443 -c 'zxid*' -S -E zxid.pem

  # Edit your /etc/hosts to contain
  127.0.0.1       localhost sp1.zxidcommon.org sp1.zxidsp.org

  # Point your browser to (zxid_simple() API version)
  https://sp1.zxidsp.org:8443/zxidhlo?o=E
  https://sp1.zxidsp.org:8443/zxidhlo.pl?o=E       # Perl version
  https://sp1.zxidsp.org:8443/zxidhlo.php?o=E      # PHP version
  http://sp1.zxidsp.org:8080/zxidservlet/zxidHLO?o=E  # Java version

  # Point your browser to (full API version)
  https://sp1.zxidsp.org:8443/zxid?o=E
  https://sp1.zxidsp.org:8443/zxid.pl?o=E       # Perl version
  https://sp1.zxidsp.org:8443/zxid.php?o=E      # PHP version
  https://sp1.zxidsp.org:8443/zxid-java.sh?o=E  # Java version

  # Find an IdP to test with and configure it...

2.1 Prerequisites

This software depends on the following packages:

  1. zlib from zlib.net. Generally whatever comes with your distro is sufficient.

  2. openssl-0.9.8d or later. See www.openssl.org. Generally openssl libraries distributed with most Linux distros are sufficient. ((It is
 possible to compile without OpenSSL, e.g. for space constrained embedded
 system, but this has serious security implications.))

  3. libcurl from http://curl.haxx.se/. I used version 7.15.5, but probably whatever ships with your distribution is fine. libcurl is needed for SOAP bindings and for fetching metadata. It needs to be compiled to support HTTPS. ((Compilation without libcurl is possible
 with some loss of functionality.))

  4. HTTPS capable web server. For most trivial testing CGI support is needed. We recommend mini_httpd(8) available from http://www.acme.com/software/mini_httpd/

  5. Perl, PHP, and Java interfaces depend on the respective development tools but should not need any additional modules or tools.

Following additional packages are needed by developers who wish to build from scratch, including the code generation (the standard distribution includes the output of the code generation, so most people do not need these).

  1. gperf from gnu.org (only for build process when generating code)

  2. swig from swig.org (only for build process and only if you want scripting interfaces)

  3. perl from cpan.org (only for build process and only if you want to generate code from .sg)

  4. plaindoc from http://mercnet.pt/plaindoc/pd.html (only for build process, for code generation from .sg, and for documentation)

Although technically not needed to build zxid, you will need an IdP to test against. We do not, at the time, supply one, so you will need to find a third party, perhaps a free download of one of the commercial ones like

2.2 Canned Tutorial: Running ZXID as CGI under mini_httpd

While zxid will run easily under Apache httpd (see recipe), for sake of simplicity we first illustrate running it with mini_httpd(8), a very simple SSL capable web server by Jef Poskanzer.

2.2.1 Getting and installing mini_httpd

You can download the source for mini_httpd from http://www.acme.com/software/mini_httpd/

You should already have installed OpenSSL, or quite probably OpenSSL shipped with your distribution. If it is not located at /usr/local/ssl, the you need to edit the mini_httpd Makefile to indicate where it is. At any rate you need to uncomment all lines that start by SSL_ in the Makefile. Then say

  make

Now copy the mini_httpd binary somewhere in your path.

2.2.2 Running mini_httpd

After building zxid, cd to zxid directory and run

  mini_httpd -p 8443 -c 'zxid*' -S -E zxid.pem

where

  -p 8443      specifies the port to listen to
  -c 'zxid*'   specifies that URL paths with "zxid" are CGI scripts
  -S           specifies that https is to be used
  -E zxid.pem  specifies the SSL certificate to use

See Apache recipe for alternative that avoids mini_httpd, but is more complicated otherwise.

N.B. The zxid.pem certificate and private key combo is shipped with zxid for demonstration purposes. Obviously everybody who downloads zxid has that private key, so there is no real security what-so-ever. For production use, you must generate, or acquire, your own private key-certificate pair (and keep the private key secret). See Certificates chapter for further info.

2.2.3 Accessing ZXID

Edit your /etc/hosts file so that the definition of localhost also includes sp1.zxidcommon.org and sp1.zxidsp.org domain names, e.g:

  127.0.0.1       localhost sp1.zxidcommon.org sp1.zxidsp.org

Point your browser to

https://sp1.zxidsp.org:8443/zxid

or if you do not want the common domain cookie check

https://sp1.zxidsp.org:8443/zxid?o=E

Dynamic linking problems

If accessing the URL (while running mini_httpd) you get no error message and no content - everything just mysteriously fails - you may be hitting a dynamic linking problem. If mini_httpd(8) fails to launch CGI script it will silently fail. This is unfortunate, but I guess that is what the "mini" in the name implies.

To make matters even worse, mini_httpd(8), probably in the interest of security, will ignore LD_LIBRARY_PATH variable. Apparently it has its fixed notion of the library paths that is set at compile time.

If you suspect this problem, try following:

  1. Create shell script called test.sh:

         #!/bin/sh
         echo Content-Type: text/plain
         echo
         echo Test $$
         echo lib_path is --$LD_LIBRARY_PATH--
         ldd zxid
         ./zxid -h 2>&1
         echo Exit value --$?--
  2. Restart mini_httpd(8) like this

         chmod a+x test.sh
         mini_httpd -p 8443 -c test.sh -S -E zxid.pem -l mini.out
  3. Access https://sp1.zxidsp.org:8443/test.sh - you may see something like

         Test 1655
         lib_path is --/usr/local/lib:/usr/lib--
         ./zxid: error while loading shared libraries: libcurl.so.3: cannot
             open shared object file: No such file or directory

    Now you at least see why it's failing (in this case the directory where libcurl was installed is not in mini_httpd's notion of LD_LIBRARY_PATH). If the zxid binary runs fine from comman line, try `ldd zxid' to see where it is finding its libraries.

Easiest dirty fix is to copy the missing libraries to one of the hardwired directories of mini_httpd(8) (e.g. /usr/lib). More sophisticated fixes include using ldconfig(8), recompiling your mini_httpd(8), or statically linking the offending library into zxid binary.

2.2.4 Setting up an IdP

Currently zxid does not ship with an IdP (though the necessary protocol encoders and decoders are latently available in libzxid, should anyone wish to make an attempt to hack an IdP together). For you to test zxid, you will need to acquire an IdP from somewhere - any vendor whose product is SAML 2.0 certified will do. Possible sources are

If you do not want to install an IdP yourself (even for testing), find someone who already runs one and ask if they would be willing to load the metadata of your zxid SP. If you do this, you will need to get externally visible domain names. This canned tutorial uses /etc/hosts (see previous step) which is only visible on your own machine.

Once you get your IdP up and running, you need to make sure it accepts the zxid SP in its Circle of Trust (CoT). This is done by placing the metadata of the SP in right place in the IdP product configuration. If your IdP supports automatic CoT management, just turn it on and chances are you are done. ((On production IdP you should
 understand the trust implications (i.e. no trust) of flipping automatic
 CoT management on.))

If not, you can obtain the zxid SP metadata (which is slightly different for each install so you can't just copy it from existing install) from

https://sp1.zxidsp.org:8443/zxid?o=B

This URL is the well known location method metadata URL. It is also the SP Entity ID or Provider ID, should the IdP product ask for this in its configuration. If the IdP product needs you to supply the metadata manually as an xml file, just point your web browser to the above URL and save to file, or use curl(1) or wget(1).

zxid SP, by default, has automatic fetching of IdP metadata enabled so there is no manual configuration step needed, provided that the IdP supports the well known location method. All SAML 2.0 certified IdP implementations must support it (but you may still need to enable it in configuration). See [SAML2meta] section 4.1 "Publication and Resolution via Well-Known Location", p.29, for normative description of this method.

However, you will need the Entity ID (Provider ID) of the IdP. This is the URL that the IdP uses for well known location method of metadata sharing. You may need to dig the IdP documentation or GUI for a while to find it. If you already have the IdP metadata as an xml file, open it and look for EntityDescriptor/entityID. If you already have the file, you can also import it manually by running the following command

  ./zxid -import file:///path/to/idp-meta.xml

But the preferred method still is: just let the automatic method do its job.

2.2.5 Your first SSO

  1. Start at

    https://sp1.zxidsp.org:8443/zxid

    or

    https://sp1.zxidsp.org:8443/zxid?o=E

    If you had common domain cookie already in place, and you are already logged in the IdP, the SSO may happen automatically (go to step 3). The automatic experience will be typical when you use SSO regularly for more than one web site (i.e. several SPs).

    However, if you get a screen titled "ZXID SP SSO", you need to paste the IdP's Entity ID to the supplied field and click "Login". If zxid SP already obtained the metadata for the IdP, you may also see a button specific for your IdP (and in this case there is no need to know the Entity ID anymore or paste anything).

  2. Next step depends on the IdP product you are using. Usually a login screen will appear asking for user name and password. Supply these and login. You will need an account at the IdP.

  3. For more slick IdPs, that's all you need to do and you will land right back at the zxid SP page titled "ZXID SP Management".

    Congratulations, you have made your first SSO!

    However, some IdPs will pester you with additional questions and you will have to jump through their hoops. A typical question is whether you want to accept a federation. You do.

    Sometimes the federation question does not appear automatically and you need to figure out a way to create a federation in their user interface and how to get them to send you back to the SP. Sometimes the word used is "account linking" instead of federation. ((Vendor products are constantly
 improving in this area. From protocol perspective
 all the additional gyrations are unnecessary. Be sure
 to provide feedback to the vendor so that simpler, easier
 to use, products will emerge in future.))

3 Configuring and Running

ZXID ships with working demo configuration so you can run it right away and once you are familiar with the concepts, you can return to this chapter.

ZXID uses a configuration file in default path ((See Simple
 API for description on how to change this path at deplyment or run
 time.))

  /var/zxid/zxid.conf

for figuring out its parameters. If this file is not present, built-in default configuration is used (see zxidconf.h). ((You can
 override configuration options at run time by supplying fragments of
 configuration using -O flags, but for CGI use you would have to use a
 wrapper shell script to supply them. Hence, easier to just use the
 config file.)) The built-in configuration will allow you to test features of ZXID, but should not be used in production because it uses default certificates and private keys. Obviously the demo private key is of public knowledge since it is distributed with the ZXID package, and as such it provides no privacy protection what-so-ever. For production use you MUST generate your own certificate and private key.

Usually configuring a system involves following tasks

  1. Configure web server (see your web server documentation)

    1. HTTPS operation and TLS certificate. In the minimum you need the main site, but you may want to configure the Common Domain Cookie virtual host as well.

    2. Arrange for ZXID to be invoked. This could mean configuring zxid, zxid-java.sh, or zxid.pl to be recognized as a CGI script, or it could mean setting up your mod_perl or mod_php system to call ZXID at the appropriate place.

  2. Configure ZXID, including signing certificate and CoT with peer metadata

    1. generate or acquire certificate

    2. Obtain peer metadata (from their well known location) or enable Auto CoT feature.

  3. Configure CoT peers with your metadata. They can download your metadata from your well known location (which is the URL that is your entity ID). For this to happen you need to have web server and ZXID up and running.

3.1 Configuration Parameters

3.1.1 zxidroot (PATH configuration parameter)

The root directory of ZXID configuration files and directories. By default this is /var/zxid and has following directories and files in it

  /var/zxid/
   |
   +-- zxid.conf  Main configuration file
   +-- pem/       Our certificates
   +-- cot/       Metadata of CoT partners (metadata cache)
   +-- ses/       Sessions
   `-- log/       Log files, pid files, and the like

3.1.2 pem

Directory that holds various certificates. The certificates have hardwired names that are not configurable.

ca.pem

Certification Authority certificates. These are used for validating any certificates received from peers (other sites on the CoT). The CA certificates may also be shipped to the peers to facilitate them validating our signatures. This is especially relevant if the certificate is issued by multilayer CA hierarchy where the peer may not have the intermediate CA certificates.

sign-nopw-cert.pem

The signing certificate AND private key (concatenated in one file). The private key MUST NOT be encrypted (there will not be any opportunity to supply decryption password).

enc-nopw-cert.pem

The encryption certificate AND private key (concatenated in one file). The private key MUST NOT be encrypted (there will not be any opportunity to supply decryption password). The signing certificate can be used as the encryption certificate. If encryption certificate is not specified it will default to signing certificate.

In addition to the above certificates and private keys, you will need to configure your web server to use TLS or SSL certificates for the main site and the Common Domain site. We suggest the following naming

ssl-nopw-cert.pem

SSL or TLS certificate for main site. In order to avoid browser warnings, the CN field of this certificate should match the domain name of the site. The SSL certificate can be same as signing or encryption certificate.

cdc-nopw-cert.pem

SSL or TLS certificate for Common Domain Cookie introduction site. In order to avoid browser warnings, the CN field of this certificate should match the domain name of the site. The SSL certificate can be same as signing or encryption certificate.

3.1.3 cot

Directory that holds metadata of the Circle of Trust (CoT) partners. If Auto CoT is enabled, this directory needs to be writable at run time.

Typical metadata file path would be

  /var/zxid/cot/Inkl5fOnhVNa0LbWjHem2Y2UphY

If the metadata file appears in this directory, then the entity is in the CoT.

The file name component of the path is safe base64 encoded SHA1 hash of the Entity ID, with last character stripped (that character would always be an equals sign).

3.2 Configuration File Format

During zxid project development phase (ongoing as of Jan 2007), most configuration related documentation will be kept as comments in zxidconf.h, which you should see.

Configuration file is line oriented. Comments can be introduced with cardinal (#) and empty lines are ignored. End of line comments are NOT supported at this time.

Each configuration option is a name=value pair. The name is the same as in zxidconf.h except that ZXID_ prefix does not appear. Only single line values are supported, but you can embed characters using URI encoding, e.g. %0a for newline. In fact, the configuration lines are treated as CGI variables, thus & can also be used as separator instead of newline (and needs to be encoded if not intended as separator).

When option is not specified, the default from zxidconf.h prevails. Thus in the following the PATH specification is redundant.

To give some idea consider following /var/zxid/zxid.conf example:

  # Demo /var/zxid/zxid.conf file
  PATH=/var/zxid
  URL=https://sp.mydomain.com:8443/zxid
  NICE_NAME=My SP's human%0areadable name.
  #EOF

4 Logging and Audit

N.B. zxidconf.h contains a wealth of logging related config options.

Tip: Your web server also has logging options. You may want to correlate the web server logs with zxid audit logs.

In serious use of SSO it is fundamental that the relying party, the SP, WSC, or WSP, archives the digitally signed evidence that justifies its actions. Generally this means that at least the SSO or credential assertions have to be archived. Quite often, especially in the WSC world, the entire SOAP response (which may be partially signed) needs to be preserved as a proof of an authorized action or attested attributes.

To lesser extent, it is also important that the issuing party, the IdP (or sometimes the DS, PS, WSC, or WSP), keeps records so that it can confirm or refute the claims of the relying party -- in the minimum it should be able to refute any obviously false claim and it should be able to detect breaches of its own security arrangements, e.g. situations where somebody is signing messages in its name although internal audit trail demonstrates this to be impossible. The IdP audit trail consists of preserving any (signed) request made by anyone as well as preserving every (signed) response it makes.

Generally every assertion, request, and response will have its unique ID that can be used as the primary key, or filename, for storing it in a database. Unfortunately these namespaces ((Namespace, as
 used here, has nothing to do with XML namespaces.)) are not disjoint (it is not very well specified in any of the standards how they interact or how wide their uniqueness properties are). ((Many
 rational implementations use 128 bit random identifiers, which
 statistically guarantees that there will not be collisions, but
 unfortunately we can not rely on other parties to adopt this
 behaviour.)) The only safe assumption is the pessimistic one: each type of object observes a unique namespace only towards its issuer and type and hence we need to map such namespaces to subdirectories.

4.1 Filesystem Layout for Logs

Please consider following layout of the log directory:

  /var/zxid/
   |
   +-- zxid.conf  Main configuration file
   +-- pem/       Our certificates
   +-- cot/       Metadata of CoT partners (metadata cache)
   +-- ses/       Sessions
   `-- log/       Log files, pid files, and the like
        |
        +-- issue/
        |    |
        |    +-- SHA1NAME/   Evidence given to an entity ID is kept in this directory
        |    |    |
        |    |    +-- a7n/   Assertions issued to the given 3rd party, named by AssertionID
        |    |    `-- msg/   Messages of any type issued to the given 3rd party, named by MessageID
        |   ...   
        |    `-- SHA1NAME2/  Evidence relating to another entity ID
        |
        +-- rely/
        |    |
        |    +-- SHA1NAME/   Evidence relating to given entity ID is kept in this directory
        |    |    |
        |    |    +-- a7n/   Assertions from 3rd parties, named by AssertionID
        |    |    `-- msg/   Messages of any type from 3rd parties, named by MessageID
        |   ...   
        |    `-- SHA1NAME2/  Evidence relating to another entity ID
        |
        +-- tmp/             Subdirectory used for atomic operations à la Maildir
        +-- act              Global activity log
        +-- err              Global error log
        `-- debug            Global debugging log

4.2 Log Line Format

The log file is line oriented, one record per line irrespective of line length, and plain text: binary data is generally omitted or represented as (safe) base64. Fields are separated by exactly one space character (0x20), except for the last free format field. Records are separated by exactly one new line (0x0a) character (never by CRLF sequence).

The log file format supports

  1. Plain text logging

  2. Signed plain text logging using either RSA-SHA1 or DSA-SHA1

  3. Symmetrically encrypted logging using either 3DES or AES

  4. Asymmetrically encrypted logging using RSA (or DSA?)

  5. Signed and symmetrically encrypted logging

  6. Signed and Asymmetrically encrypted logging

All activity and error log file lines have the following format (any one of the 3):

  # comment
  SE CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
  SE SIG OURTS SRCTS IP:PORT SUCCEID MID A7NID NID VVV RES OP PPP FMT

where

SE

Log signing and encryption designator. In all cases the actual signing or encryption key is not identified on the log line. This will need to be determined out-of-band.

PP

PlainPlain: not signed and not encrypted

Rx

RSA-SHA1 signed (x = any encryption)

Dx

DSA-SHA1 signed

Sx

SHA1 check-summed, but not signed (SSSS is the checksum)

xA

Asymmetrically AES encrypted (x = any signing method)

xT

Asymmetrically 3DES encrypted

xB

Symmetrically AES encrypted (theoretical: how to safeguard the key?)

xU

Symmetrically 3DES encrypted (theoretical: how to safeguard the key?)

xZ

[RFC1951] zipped (not really encryption)

Xxx

Experimental arrangements.

CCCC

Safe base64 encoded log encryption blob. In case of encryption blob, the rest of the log fields will not appear. Decrypted logline will contain fields starting from SSSS.

SIG

Safe base64 encoded log line signature blob. If no signature, this is a dash ("-").

OURTS

Our time stamp, format YYYYMMDD-HHMMSS.TTT where TTT are the milliseconds. The time is always in GMT (UTC, Zulutime).

SRCTS

Source time stamp, format YYYYMMDD-HHMMSS.TTT. If TTT was not originally specified it is represented as "501". The time is always in GMT (UTC, Zulutime).

IP:PORT

The IP address and the port number of the other end point (usually client, but could be spoofed, caveat emptor).

SUCCEID

The SHA1 name of the entity (succinct entity ID without the equals sign).

MID

Message ID relating to the log line. Allows message to be fetched from the database or the file system. Any relates-to or similar ID is only available by fetching the original message. Dash ("-") if none.

A7NID

Assertion ID relating to the log line. Allows assertion to be fetched from the database or the file system. If message benefits from multiple assertions, this is the one relating to the outermost one. Other A7NIDs are only available by fetching the original assertion. Dash ("-") if none. If the assertion is encrypted and can not be decrypted, then placehoder "-enca7n-" is used.

NID

IdP assigned NameID relating to the message, if any. If the NameID is encrypted and can not be decrypted, then placeholder "-encnid-" is used.

VVV

Signature validation codes

O

Capital Oh (not zero). All relevant signatures validate (generally assertion)

A

Unsupported or bad signature or message digest algorithm

G

Checksum of XML DSIG does not validate

R

The RSA layer of the signature does not validate

N

No signature detected.

I

Issuer metadata not found (or not in CoT, or corrupt metadata).

V

Assertion validity error (e.g. not in time range or wrong audience)

F

Operation failed or faulted by error code (low level protocol ok)

Exx

Extended signature validation code (generally failure)

Xxx

Experimental signature validation code (generally failure)

RES

Result of the operation.

K

Operation was success

C

Operation failed because client did not provide valid input

S

Operation failed due to server side error

P

Operation failed due to policy or permissions issue

T

Temporary error, client was encouraged to retry

B

Metadata related error (no metadata or parse error in metadata)

D

Redirect or recredential. Client was encouraged to retry.

W

Way point message. Neither success nor failure.

Exx

Extended result (generally failure)

Xxx

Experimental result (generally failure)

OP

The documented operation

FEDNEW

Federation and SSO request succeeded, new federation was created.

FEDSSO

SSO using federated ID was performed

TMPSSO

SSO using temporary NameID was performed

SLO

Single Logout was completed

DEFED

Defederation was performed

BADCF

Server configuration (/var/zxid/zxid.conf) is bad

NOMD

No metadata found after options exhausted (cache, fetch from net)

BADMD

Metadata parsing error

BADXML

XML parsing error in protocol

SAMLFAIL

SAML call failed (often SOAP call)

ERR

Other error

For WSP the OP is the command verb that was exercised.

For WSC the OP is the command verb preceded by capital C, e.g. "CQuery".

Additional OP verbs may need to be specified for protocol substeps like artifact resolution (ART) and direct authentication (AUTH).

ART

Artifact resolution request sent with SOAP (1)

ANREDIR

Redirection with Authentication Request

LOCLO

Local Logout (1)

SLOREDIR

Redirection with Single Logout Request

MNIREDIR

Redirection with Manage NameID Request for changing NameID

DEFEDREDIR

Redirection with Manage NameID Request for defederation

SLOSOAP

Single Logout Request SOAP call made

MNISOAP

Manage NameID Request for changing NameID SOAP call

DEFEDSOAP

Manage NameID Request for defederation SOAP call

SAMLOK

SAML call OK (often SOAP call)

Additional OP verbs may need to be specified for other logging operations like regular web access logs (HEAD, GET, POST).

IDPSEL

IdP Selection screen is shown (2)

MGMT

Management screen is shown (2)

SHOWPC

Logged in (by SSO or session). Show protected content. arg is sid. (1)

SPDISP

SP Command Dispatch (received POST or redir) (2)

MYMD

My metadata was served to requester on the net (1)

GETMD

Getting metadata from net (2)

GOTMD

Got metadata from net (1)

BADCGI

Unknown CGI options (0, but not implemented yet)

PPP

Operation dependent one most relevant parameter. Dash ("-") if none.

FMT

Operation dependent free-form data. May contain spaces. Dash ("-") if none.

4.3 Log Signing and Encryption

Logs are enabled in the config file zxidconf.h (compile time) by ZXLOG macros which provide default values for the log flags in struct zxid_conf. Each log flag is a bitmask of signing and encryption options. Zero value means no logging. "1" can be used to enable plain text logging.

Log signing may help you to argue that log evidence was (not) tampered with. You can configure the signing level in the config file zxidconf.h (compile time):

0

no signing (Px)

2

sha1 MD only (Sx)

4

RSA-SHA1 (Rx)

6

DSA-SHA1 (Dx)

For actual signing (options 2 and 3), the private key for signing must be available in /var/zxid/pem/logsign-nopw-cert.pem. Note that this file need not contain the actual certificate (but it may, it just will not be used).

The weak point of log signing is that if the private key is stolen, then someone can create falsified logs and the private key needs to be available on the point where the logs are generated - thus it is actually quite vulnerable.

Log encryption may help to keep the logs confidential. You can configure the configuration level in the config file zxidconf.h (compile time):

0x00

no encryption (xP)

0x10

[RFC1951] zip - safe-base64 [RFC3548] (xZ)

0x20

RSA-AES (xA)

0x30

RSA-3DES (xT)

0x40

Symmetric AES (xB)

0x50

Symmetric 3DES (xU)

For RSA modes the public key for encryption must be available in /var/zxid/pem/logenc-nopw-cert.pem. Note that the private key should NOT be kept in this file: the whole point of public key encryption is that even if your server machine is stolen, the bad guys can't access the logs - if the private key was anywhere in the stolen machine, they will find it.

For symmetric encryption the key is the SHA1 hash of file /var/zxid/pem/logenc.key. Obviously this key must be kept secret, but see the caveat about stolen machine in the previous paragraph.

All encryption modes, except for 0, [RFC1951] zip compress the log line before encryption and safe-base64 encode the result of the encryption. All encryption modes, except 0 and 1, prefix the zipped log line with 128 bit nonce before encrypting.

The algorithm is roughly

  1. If encrypt, zip the raw log line

  2. If sign, compute the signature (over zipped version if applicable)

  3. Prepend signature blob to log line. If encrypting, the signature is embedded in binary form, otherwise it is embedded in safe-base64 form.

  4. If encrypt, perform the encryption.

  5. If encrypt, apply safe-base64.

The supplied tool zxlogview(1) allows the logs to be decrypted and the signatures verified.

  ./zxlogview logsign-nopw-cert.pem logenc-nopw-cert.pem <some-log-lines

Note that for zxlogview(1) to work the logsign-nopw-cert.pem needs to contain the public key (and need not contain the privatekey) which is the opposite of the situation what zxid(1) needs to see in order to sign. Similarly logenc-nopw-cert.pem needs to contain the private key (and may contain the certificate, though this will not be used).

N.B. While encrypted logs are cool, you should evaluate the gain against the incovenience: if you encrypt them, the lesser mortal sysadmins may not be able to debug your installation because they do not know how to decrypt logs or you are not willing to trust them with the keys. For this reason, you can configure the encryption of error log separately.

4.4 Internal Crypto Formats

For [RFC1951] zipped safe-base64 [RFC3548] output the input to base64 encoding is

  LLSSSSZZZZZZZZZZZZZZ    -- RFC1951 zipped safe-base64

For encrypted modes the input to AES (or other symmetric cipher) is

  NNNNLLSSSSZZZZZZZZZZ    -- Note how nonce is prepended

The NNNN is used as initialization vector and actual encryption encompasses LL, SSSS, and ZZZZ.

In RSA-AES the session key is encrypted using RSA and prepended to the input for base64 encoding.

  KKEEEECCCCCCCCCCCCCC    -- RSA-AES: note prepended session key
NNNN

16 bytes of nonce. This is used as initialization vector

       for AES or 3DES cipher operated in CBC mode.
LL

Bigendian integer representing signature length in bytes.

       0 means none. Negative values reserved for future use.
SSSS

The signature in binary

ZZZZ

[RFC1951] zipped safe-base64 [RFC3548] of the payload

KK

Bigendian integer representing encrypted session key

       length in bytes. Negative values are reserved for future use.
EEEE

RSA encrypted session key in binary

CCCC

Ciphertext from the symmetric cipher, including nonce.

In RSA operations RSA_PKCS1_OAEP_PADDING padding is used (PKCS #1 v2.0).

4.5 Logging Assertions

Logging of assertions is controlled by configuration options ZXLOG_ISSUE_A7N and ZXLOG_RELY_A7N. At least ZXLOG_RELY_A7N should be turned on for ID-WSF web services to work correctly. Logging relied assertions also allows detection of duplicate assertion IDs. Logging, or not, of issued assertions does not have any operational effect and is only for audit trail purposes.

Assertions are logged in directories depending on issuer's sha1 name.

  /var/zxid/log/rely/ISSUER-SHA1-NAME/a7n/A7N-ID-AS-SHA1

Sha1 names are used to avoid any attack through issuer entity ID or the assertion ID being evilly crafted to contain shell metacharacters or filesystem significant characters.

Assertions issued by ourselves follow similar pattern

  /var/zxid/log/issue/DEST-SHA1-NAME/a7n/A7N-ID-AS-SHA1

If the logfile starts by less-than character ("<") then it is in plain text. Encrypted or signed formats will start in another way, but are not specified at this time.

N.B. The relied-on assertions may be referenced from session objects and used in construction of credentials for ID-WSF based web services calls. Therefore the rely directory should not be cleaned too aggressively: the assertions must remain there until the referencing session expires.

4.6 Logging Requests and Responses

Logging of requests and responses is controlled by ZXLOG_ISSUE_MSG and ZXLOG_RELY_MSG. Logging, or not, messages has no operational effect and is only for audit trail purposes. If logging of relied messages is turned on, then it is possible to detect duplicate message IDs.

Request messages are logged in directories depending on issuer's sha1 name.

  /var/zxid/log/rely/ISSUER-SHA1-NAME/msg/REQ-ID-AS-SHA1

Sha1 names are used to avoid any attack through issuer entity ID or the assertion ID being evilly crafted to contain shell metacharacters or filesystem significant characters.

Responses issued by ourselves follow similar pattern

  /var/zxid/log/issue/DEST-SHA1-NAME/msg/RESP-ID-AS-SHA1

If the logfile starts by less-than character ("<") then it is in plain text. Encrypted or signed formats will start in another way, but are not specified at this time.

4.7 Session Storage and Bootstraps

The ZXID session system serves three purposes:

  1. Remember whether user has logged in. The session ID is carried either in a cookie or as part of the URL.

  2. Make it possible to perform Single Logout (SLO) and certain federation management tasks.

  3. Remember the service end points (EPRs) that were either

    1. supplied as bootstrap attributes in the SSO assertion, or

    2. later discovered

The biggest complication is the requirement to remember the EPRs and the solution currently used is to keep them as files in a per session directory under the /var/zxid/ses tree.

  /var/zxid/
   |
   +-- zxid.conf  Main configuration file
   +-- pem/       Our certificates
   +-- cot/       Metadata of CoT partners (metadata cache)
   +-- ses/       Sessions
   |    |
   |    +-- SESID/         Each session has its own directory
   |         |
   |         +-- .ses      The session file
   |         +-- SVC,SHA1  Each bootstrap is kept in its own file
   |
   +-- user/      Local user accounts (if enabled)
   |    |
   |    +-- SHA1/ Each local user has a directory whose name is SHA1
   |         |    of the user's NameID (idpnid)
   |         +-- .mni     Information needed by Name ID management
   |
   `-- log/       Log files, pid files, and the like

4.7.1 Session directory

The session ID is an unguessable (but see ID_BITS configuration options) safe base64 encoded pseudorandom number. Unguessability ensures that the session can only be crated via SSO.

The service EPRs are XML documents whose name is composed from two components

  SVC,SHA1
SVC

The service type URI, with file system unsafe characters (e.g. "/" and ",") folded to underscore ("_"). Purpose of the SVC is to allow quick identification, without opening, of the files that contain EPRs for a given service type. Only first 200 bytes of the service type are used.

SHA1

safe base64 encoded SHA1 hash of the content of the EPR. The purpose of the SHA1 hash is to produce a unique identifier so that two distinct EPRs for same service will have different file names.

The session directory also contains .ses file. The first line is as follows (still subject to change, Oct 2007):

  NameID|a7n-ref

The pipey symbol (|) is a field separator. Future versions may define further fields beyound these original two. All other lines are reserved for future expansion. Fields:

NameID

NameID, extracted during SSO

a7n-ref

Filesystem path to the SSO assertion.

4.7.2 User directory

User directories are used for storage of local account information. Since many web applications, to which ZXID may be integrated, already have their own local user storage, the ZXID user directory is optional, see USER_LOCAL configuration option.

IdP initiated ManageNameID requests depend on local user accounts, so if you want this to work you need to enable them. Local user account management may be useful on its own right if your application does not yet have such system. If it has, you probably want to continue to use the application's own system.

Each user is represented by a file whose filename is safe base64 of the SHA1 hash of the user's NameID.

Inside the directory, a file called .mni captures the information needed for NameID Management. It is expected that other files about the user may be populated to capture other aspects. Your own applications could even create files here.

The first line of the .mni file is as follows

  FMT|IDPEnt|SPqual|NameID|MNIptr

The pipey symbol (|) is a field separator. Future versions may define further fields beyound these original two. All other lines are reserved for future expansion. Fields:

FMT

NameID Format

IDPent

IdP entity ID that qualifies the NameID (namespace if you like). This usually corresponds to the NameQualifier of <NameID>

SPqual

SP entity or affilitation ID (optionally) sent by IdP. This further qualifies the namespace of the Name ID.

NameID

NameID of the account

MNIptr

If NameID Management has been used to change the IdP assigned NameID, then the new NameID. There will be a local user account directory for the new NameID. Consider this as a sort of symlink functionality.

5 Compilation for Experts

  make cleaner
  make dep ENA_GEN=1
  make
  make all ENA_GEN=1

5.1 Build Process

The build process of ZXID relies heavily on code generation techniques that are not for the faint of heart. Some of these techniques, like xsd2sg.pl were innovated for this project, while others like SWIG and gperf are existing software. Here and there some additional perl(1) and sed(1) scripts are run to fix a thing or two.


Fig-1: ZXID Build Process

Carefully study the Makefile, and this should all start to make sense. Please note that there is no configuration script and GNU Auto-tools (GNU Autohell) are not used. This is because they have been evaluated to be unsuitable to this project (or to author's tastes). If the Makefile does not do what you want, you should first study if you can change necessary variables using localconf.mk, or if that does not work, then just edit the Makefile itself. You can temporarily customize the variables in the Makefile by providing new value on command line, e.g.

  make CC=cc   # override the default of CC=gcc

5.2 Special or embedded compile (reduced functionality)

libzxid contains thousands of functions and any given application is unlikely to use them all. Thus the easiest, safest, no loss of functionality, way to reduce the footprint is to simply enable compiler and linker flags that support dead function elimination. ((Unfortunately the gnu ld does not support dead
 function elimination. You should file this as a bug to them. If they
 tell you to put every one of the 7000-some functions in a separate <tt>.c</tt> file,
 consider the scalability implications of this. Read the comments in
 <tt>pulverize.pl</tt> for a full scoop and an approach.))

On gcc you should investigate compilation with "-ffunction-sections -fdata-sections" and linking with "-Wl,--gc-sections".

If you need to squeeze zxid into as minimal space as possible, some functionality trade-offs are supported. I stress that you should only attempt these trade-offs once you are familiar with zxid and know what you are doing. The canned install instructions and tutorial walk thrus stop working if you omit significant functionality.

5.2.1 Compilation without OpenSSL

Comment out the -DUSE_OPENSSL flag from CFLAGS in Makefile and recompile.

This will cripple zxid from security perspective because it will no longer be able to verify or generate digital signatures. Unless your environment does not need trust and security, or you understand thoroughly how to provide trust and security by other means, it is a very bad idea to compile without OpenSSL.

N.B. Compiling, or not, zxid with OpenSSL does not affect whether your web server will use SSL or TLS. Unless you know what you are doing, you should be using SSL at web server layer. Given that SSL is used at web server layer, the memory footprint savings you would gain from compiling zxid without OpenSSL may be negligible if you use dynamic linking.

5.2.2 Compilation without libcurl

Comment out the -DUSE_CURL flag from CFLAGS in Makefile and recompile.

Disabling libcurl does not have adverse security implications: you only loose some functionality and depending on your situation you may well be able to live without it.

  1. Without libcurl, zxid can not act as a SOAP client. This has a few consequences

    1. Artifact profile for SSO is not supported because it needs SOAP to resolve the artifact. In most cases a perfectly viable alternative is to use POST profile for SSO.

    2. SOAP profiles for Single Logout and NameID management (aka defederation) are not supported. You can use the redirect profiles and get mostly the same functionality.

  2. Automatic CoT metadata fetching using well known location method is not supported without libcurl. You can fetch the metadata manually, e.g. using web browser, and place it in /var/zxid/cot directory.

    If you want to manually control your Circle of Trust relationships, you probably want to do this anyway so loss of automatic functionality may be a non-issue. ((If you compile with libcurl, but still want to disable
 automatic metadata fetching, investigate the ZXID_MD_FETCH
 and related configuration options.))

  3. Web Services Client (WSC) functionality is not supported without libcurl. Effectively this is just another case of "SOAP needed". If you have your own SOAP implementation, you may, at lesser automation, achieve much of the same functionality by calling the encoder and decoder functions manually.

5.2.3 Compiling without zlib (not supported)

zlib is used mainly in redirect profiles. Since zlib is widely avvailable and its foot print is small, we have made no supported provision to compile without it. If you hack something together, let us know.

5.3 Choosing Which Standards to Compile in (default: all)

WARNING: Only regularly tested configuration is to compile all standards in.

On space constrained systems you may shed additional weight by only compiling in the IdM standards you actually use. Of course, if you do not use them, the dead function elimination should take care of them, but sometimes you can gain additional savings in space and especially compile time.

Another reason could be, in the land of the free, if some modules are covered by a software patent, you may want to compile a binary without the contested functionality. ((Please do not
 ask me to add additional baggage to avoid patents. Software
 patents are a plague and your efforts are best spent in getting
 them overturned or changing laws so they go away.))

You can tweak the flags, shown in accompanying table, in the Makefile or by supplying new values in localconf.mk or on commend line. For example

  make TARGET=sol8 ENA_SAML2=0

would disable SAML 2.0 (and trigger build for Sparc Solaris 8).

Table 1:Conditional inclusion of standards
Makefile flag Standard Comments
ENA_SSO=1 All SSO Must be enabled for any of SSO to work
ENA_SAML2=1 SAML 2.0  
ENA_FF12=1 ID-FF 1.2 Requires ENA_SAML11=1
ENA_SAML11=1 SAML 1.1  
ENA_WSF=1 All WSF Must be enabled for any of WSF to work
ENA_WSF2=1 ID-WSF 2.0  
ENA_WSF11=1 ID-WSF 1.1  

5.4 localconf.mk

You can use localconf.mk to remember your own make options, such as TARGET and different ENA flags, without editing the distributed Makefile.

One useful option to put in localconf.mk is ENA_GEN which will turn on the dependencies that will trigger generation of the files in zxid/c directory. For example

  echo 'ENA_GEN=1' >>localconf.mk
  make
WARNING: If you are confused about compilation flags appearing "out of nowhere", despite not being mentioned in the Makefile, be sure you have not inadvertently created a localconf.mk (perhaps you rsynced the sources from another machine and forgot to remove localconf.mk). A similar problem can occur if you accidentally copied deps.dep from another machine. It will reference the dependencies with the paths of that machine. Just remove the deps.dep to solve this issue.

5.5 Tough Compilation Errors

ZXID was mostly developed on gcc 3.4.6 and explicitly has not been tested on 4.x series of gcc (due to deprecation of commonly useful features in that gcc series). If you hit compile time problems with 4.x gcc, try with 3.4.x series gcc first, before reporting bugs.

***

5.6 Tough Linking Errors

5.7 Overview of the xsd2sg.pl Tool

The xsd2sg.pl tool, distributed as part of Plaindoc (http://mercnet.pt/plaindoc/pd.html) system, is at the heart of the ZXID code generation. Unfortunately the tool is still in flux. What follows is general outline valid as of March 2007.

xsd2sg.pl can be invoked in three ways (see xsd2sg.pl --help), but the invocation of interest to use is the -S SG to XSD with Code generation mode. This mode takes as inputs all .sg files and names of decoding root elelment. It will generate one mega parser that is capable of decoding any root element (and since root elements contain other elements, then really all elements). The decoder works by recursive descent parser principle so there will be smaller decoder functions to call. Similarily the encoder is generated by creating many unit encoders for different elements. Then the parent elements will call the child element encoders.

5.7.1 Process .sg to create %dt hash (throw away XSD output)

xsd2sg.pl first processes the .sg files to XSD. As a side effect it populates %dt multidimentsional hash

  $dt{'element'}{elemname}[array-of-sublemens-and-attrs]
  $dt{'attribute'}{attrname} = "";
  $dt{'complexType'}{typename}[array-of-sublemens-and-attrs]
  $dt{'group'}{groupname}[array-of-sublemens-and-attrs]
  $dt{'attributeGroup'}{agname}[array-of-attrs]

Where the array-of-subelems-and-attrs can contain tag or reference to fundamental or derived type, e.g.

  "tag$sa:Assertion$min$max"
  "ref$xs:anyURI$"
  "ref$sa:Assertion$"
  "base$$0$1$base-type"
  "enum$n1$n2$n3$n4$n5"
  "any$$min$max$pc$ns"
  "anyAttribute$$min$1"
  "_d$$0$1$simplecontent"

5.7.2 The expanding element definitions in %dt

Since the concrete goal is to generate data structures for elements irrespective of how types were used to describe the data, we need to expand any type reference into element definition where it appears. This is done by expand_element() which will in its turn call expand_complex_type(), expand_group() and expand_attribute_group() to do the job.

5.7.3 Generation Phase

Once %dt is in expanded form, the get_element() is called for each XML namespace. It will generate the code for encoders and decoders for the elements in the XML namespace. Finally the root decoder is gnerated using gen_element(). Furing generation following template files are used

  aux-templ.c      - Code generation template for auxiliary functions
  dec-templ.c      - Code generation template for decoders
  enc-templ.c      - Code generation template for encoders
  getput-templ.c   - Code generation template for accessor functions

A lot of the generated headers are simply hard coded in the xsd2sg.pl (i.e. no templates).

6 Net::SAML Perl Module

After building the main zxid tree, you can

  cd Net
  perl Makefile.PL
  make
  make test      # Tests are extremely sparse at the moment
  make install

This assumes you use the pregenerated Net/SAML_wrap.c and Net/SAML.pm files that we distribute. If you wish to generate these files from origin, you need to have SWIG installed and then say in main zxid directory

  make perlmod ENA_GEN=1  # Makes all available perl modules (including heavy low level ones)
  make samlmod ENA_GEN=1  # Only makes Net::SAML (much faster)
  make wsfmod  ENA_GEN=1  # Only makes Net::WSF (much faster)
WARNING: Low level interface is baroque, and consequently, it will take a lot of disk space, RAM and CPU to build it: 100 MB would not be exaggeration and over an hour (on 1GHz CPU). Build time memory consumption of single cc1 process will be over 256 MB of RAM. You have been warned.

6.1 Current major modules are

6.2 Planned modules

6.3 Perl API Adaptations

The perl APIs were generated from the C .h files using SWIG. Generally any C functions and constants that start by zxid_, ZXID_, SAML2_, or SAML_ have that prefix changed to Net::SAML::. Note, however, that the zx_ prefix is not stripped.

Since ZXID wants to keep strings in many places in length + data representation, namely as struct zx_str, SWIG typemaps were used to make this happen automatically. Thus any C function that takes as an argument struct zx_str* can take a perl string directly. Similarly any C function that returns such a pointer, will return a perl string instead. As a final goodie, any C function, such as

  struct zx_str* zx_ref_len_str(struct zx_ctx* c, int len, char* s);

that takes length and s as explicit arguments, takes only single argument that is a perl string (the one argument automatically satisfies two C arguments, thanks to a type map). The above could be called like

  $a = Net::SAML::zx_ref_len_str(c, "foo");

First the "foo" satisfies both len and ~s~, and then the return value is converted back to perl string.

6.4 Testing Net::SAML and zxid.pl as CGI script

To test the perl module, you must restart the mini_httpd(8) so that it recognizes zxid.pl as CGI script:

  mini_httpd -p 8443 -c zxid.pl -S -E zxid.pem

Then start browsing from

  https://sp1.zxidsp.org:8443/zxid.pl

or if you want to avoid the common domain cookie check

  https://sp1.zxidsp.org:8443/zxid.pl?o=E

6.5 Testing Net::SAML and zxid.pl under mod_perl

You can run zxid.pl under mod_perl using the Apache::Registry module. See Apache recipe for how to compile Apache to support mod_perl. After configuration it should work the same as the CGI approach.

6.6 Debugging Net::SAML with GDB

As bizarre as it may sound, it is actually quite feasible to debug libzxid and the SAML_wrap.c using GDB while in perl. For example

  cd zxid
  gdb /usr/local/bin/perl
  set env QUERY_STRING=o=E
  r ./zxid.pl

If the script crashes inside the C code, GDB will perfectly reasonably take control, allowing you to see stack back-trace (bt) and examine variables. Of course it helps if openssl and perl were compiled with debug symbols (libzxid is compiled with debug symbols by default), but even if they weren't you can usually at least get some clue.

When preparing a perl module, generally Makefile.PL mechanism causes the same compilation flags to be used as were used to compile the perl itself. Generally this is good, but if libzxid was compiled with different flags, mysterious errors can crop up. For example, I compile my libzxid against openssl that I have also compiled myself. However, I once had a bug where the perl had been compiled such that the Linux distribution's incompatible openssl would be picked by perl compile flags, resulting in mystery crashes deep inside openssl ASN.1 decoder routines (c2i_ASN1_INTEGER() while in d2i_X509() to be exact). When I issued `info files' in GDB I finally realized that I was using the wrong openssl library.

7 PHP extension php_zxid.so

The PHP integration is incomplete due to incomplete support in SWIG for php5. However, enough interface exists to get most high level API working and thus successfully run an SP.

7.1 Building and Installing ZXID PHP extension

After building main zxid distribution, say

  make phpzxid

You MUST have php-config(1) in path. If not, try

  make phpzxid PHP_CONFIG=/path/to/php-config

If the extension built successfully, you can use it by copying it to a suitable place, e.g.

  make phpzxid_install

The install again uses the php-config(1) to figure out where php(1) can find the module.

Next you need to decide whether to run under Apache mod_php setup (apache), or as CGI (any web server).

7.1.1 Running PHP as Apache mod_php

See Apache recipe for how to compile Apache to support mod_php.

7.1.2 Running PHP as CGI (any web server)

In the CGI case you generally make sure your CGI script starts like

  #!/usr/bin/php
  <?
  dl("php_zxid.so");  # These three lines can go to initialization: they only need to run once
  $conf = "PATH=/var/zxid/&URL=https://sp1.zxidsp.org:8443/zxidhlo.php";
  $cf = zxid_new_conf_to_cf($conf);
  ?>

The first line makes sure the file is executed with php interpreter. You should change it to match the path where your php is installed. You also need to make your CGI script executable, e.g:

  chmod a+x mycgi.php

Then you place the CGI script in a directory in the document tree of the web site and make sure your http server is configured (permitted) to execute CGI scripts in that directory.

One tricky thing that can go wrong is dynamic linking. When you compiled the php_zxid.so module, some linking dependencies are usually created. Problem arises if some of the dependencies are not in the paths allowed for dynamic linking by your web server. The paths allowed by web server can easily be different than in your shell and some web servers even ignore LD_LIBRARY_PATH environment variable. Sometimes you just have to copy the dependency libraries to one of the allowed directories. This is "dirty", but works. See ldconfig(8) and section ?ZXID-Installing-CannedTutorialRunningZXIDasCGIundermini_httpd-AccessingZXID? for further information.

You can easily see the dependencies using ldd(1)

  ldd /apps/php/5.1.6/lib/php/extensions/no-debug-non-zts-20050922/php_zxid.so
        linux-gate.so.1 =>  (0xffffe000)
        libpthread.so.0 => /lib/libpthread.so.0 (0xb7796000)
        libdl.so.2 => /lib/libdl.so.2 (0xb7792000)
        libcurl.so.3 => /apps/lib/libcurl.so.3 (0xb7611000)
        libz.so.1 => /apps/lib/libz.so.1 (0xb75fe000)
        libc.so.6 => /lib/libc.so.6 (0xb74dd000)
        /lib/ld-linux.so.2 (0x80000000)

In this example the suspect library dependencies are /apps/lib/libcurl.so.3 and /apps/lib/libz.so.1 because they are outside normal places, i.e. /lib and /usr/lib.

Another thing to remember is that CGI specification requires that the Content-Type header and an empty line (to separate headers from content) is emitted before the actual page. If this fails to happen, the page will mysteriously not appear although your script ran successfully.

Sometimes the debug output can end up in stdout (e.g. somewhere stderr was redirected to stdout) and this will garble the returned web page. Easy fix is to disable debug output by not supplying ZXID_AUTO_DEBUG. See section ?ZXID-ZXID_simpleAPI-HelloWorld-AUTOoptions?.

7.2 Programming with ZXID PHP Extension

To use the ZXID PHP extension you must add near beginning of your script

  dl("php_zxid.so");   // Load the module

You may need to tweak the paths, or LD_LIBRARY_PATH, to get this to work.

After this, you can use the PHP interface much the same way as you would use the C interface. See the distributed zxid.php and zxidhlo.php for further usage examples.

8 Python Extension

** TBD using SWIG. Very preliminary support in Makefile exists, but ** this has not been tested in any real world set up.

THe Python extension is built into subdirectory py with command

  make pyzxid

The result should be py/py_zxid.so

9 Java Native API (JNI): zxidjava package and zxidjni class

9.1 Building the JNI

After building main zxid distribution, say

  make javazxid

You must have set JNI_INC variable correctly in the Makefile (or in localconf.mk) and javac must be in path (or you must set JAVAC variable). For the servlet or Tomcat support, you must make sure SERVLET_PATH points to your servlet-api.jar file. The ZXID Java interface has been mainly tested with j2sdk1.4.2 and some versions of Java SDK 1.5.

If you have done changes that require regeneration of the zxidjava/zxid_wrap.c file you should build with

  make javazxid ENA_GEN=1

but this requires that you have swig(1) installed. Depending on the changes it may also require xsd2sg.pl and gperf(1), see "Compilation for Experts" section, above, for full explanation. As of January 2007, all of the Java JNI interface is swig(1) generated - there are no human authored files. However, we anticipate building a helper Java library to facilitate use of the JNI - contributions welcome.

After compilation, just copy the class files and the libzxidjni.so to suitable locations in your system (Makefile lacks any specific Java installation target because the author has not yet made up his mind about what makes sense). When you run Java programs that use the zxidjni class, you must makes sure the libzxidjni.so is found by the dynamic linker - usually this means setting LD_LIBRARY_PATH environment variable. The zxid-java.sh shell script demonstrates how to do this for the example CGI program zxid.java.

9.1.1 MacOS X: JNI Notes

9.2 Programming with ZXID Java API

For detailed usage examples of the Java interface you should study the zxid.java file.

The Java interface is contained in a package called zxidjava. This package contains the main wrapper class zxidjni as well as a number of data type specific classes. The zxidjni class is just a container for procedural zxid API - all methods of this class are static.

To start using the ZXID Java interface you need to do two things:

  import zxidjava.*;

somewhere near top of your program pulls in the zxidjava package, including the zxidjni class. Then you need to have a static initializer smewhere in your program to pull in the libzxidjni.so:

  public class myprog {
    static {
      System.loadLibrary("zxidjni");
    }

    public static void main(String argv[]) throws java.io.IOException
    {
      // ...
    }
  }

From here on you can call the C API procedures as static methods of the zxidjni class, e.g:

  cf = zxidjni.new_conf("/var/zxid/");

Note that the zxid_ prefix is omitted in favour of the zxidjni class name qualifier.

9.3 Known Problems and Limitations

The zx_str type is generally NOT nul terminated. We try to map these in the SWIG type maps, but any function returning char* currently maps to Java String type, yet there is no way of knowing how long the string type is. Therefore it's not safe to call functions returning char*. For example, consider

  int zx_LEN_SO_sa_Action(struct zx_ctx* c, struct zx_sa_Action_s* x);
  char* zx_ENC_SO_sa_Action(struct zx_ctx* c, struct zx_sa_Action_s* x, char* p);
  struct zx_str* zx_EASY_ENC_SO_sa_Action(struct zx_ctx* c, struct zx_sa_Action_s* x);

The intent of the LEN_SO plus ENC_SO pair is that you first compute length, allocate sufficient buffer, and then render the encoding into the buffer. The ENC_SO in fact returns char* one past the end of the string. It is NOT safe to cal ENC_SO from Java because the SWIG generated interface would make Java believe that the char* one past end of string is a C string in its own right. Thus the only safe one to call is the EASY_ENC_SO variant.

9.4 Running as servlet under Tomcat

ZXID distribution contains subdirectory called servlet. You should link this into webapps directory of Tomcat servlet container

  cd ~/apache-tomcat-5.5.20/webapps
  ln -s ~/zxid-0.17/servlet zxidservlet

You also need to set allowLinking flag in apache-tomcat-5.5.20/conf/context.xml (the reloadable flag avoids having to restart Tomcat if you recompile the .class file):

  <Context allowLinking="true" reloadable="true">...

The file servlet/WEB-INF/web.xml describes the example zxid application. The actual application lives in servlet/WEB-INF/classes which is actually just a symlink back to the top level of the ZXID distribution. Therefore the zxidhello.class file appears on the top level and the wrapper classes, which are scoped in zxidjava package, appear in zxidjava/ subdirectory. From the servlet container's perspective the directory appears to be apache-tomcat-5.5.20/webapps/zxidservlet/WEB-INF/classes/zxidjava

After make javazxid and restart of Tomcat (killall java; apache-tomcat-5.5.20/bin/startup.sh), you can access the application using URL (defined in servlet/WEB-INF/web.xml)

  http://sp1.zxidsp.org:8080/zxidservlet/zxidHLO?o=E

9.5 Troubleshooting class loader

If you get

  java.lang.ClassNotFoundException: zxidhlo
        org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1355)
        org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1201)
        org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
        org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
        org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
        org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
        org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
        org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
        org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
        java.lang.Thread.run(Thread.java:595)

Then you forgot to turn on the symlinks (see allowLinking, above).

If you get

  java.lang.UnsatisfiedLinkError: no zxidjni in java.library.path
        java.lang.ClassLoader.loadLibrary(ClassLoader.java:1517)
        java.lang.Runtime.loadLibrary0(Runtime.java:788)
        java.lang.System.loadLibrary(System.java:834)
        zxidhlo.<clinit>(zxidhlo.java:20)
        sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
        sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
        java.lang.reflect.Constructor.newInstance(Constructor.java:274)
        java.lang.Class.newInstance0(Class.java:308)
        java.lang.Class.newInstance(Class.java:261)
        org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
        org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
        org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
        org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
        org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
        org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
        org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
        java.lang.Thread.run(Thread.java:534)

Then it is not finding zxidjava/libzxidjni.so. Either say

  export LD_LIBRARY_PATH=~/zxid-0.14/zxidjava:$LD_LIBRARY_PATH

or place libzxidjni.so in CATALINAHOME/shared/lib. Note that the library name really is libzxidjni.so despite the misleading exception suggesting just "zxidjni".

If you get

  java.lang.UnsatisfiedLinkError: Native Library /home/sampo/zxid/zxidjava/libzxidjni.so already loaded in another classloader
        java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1551)
        java.lang.ClassLoader.loadLibrary(ClassLoader.java:1511)
        java.lang.Runtime.loadLibrary0(Runtime.java:788)
        java.lang.System.loadLibrary(System.java:834)
        zxidhlo.<clinit>(zxidhlo.java:20)
        sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
        sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
        java.lang.reflect.Constructor.newInstance(Constructor.java:274)
        java.lang.Class.newInstance0(Class.java:308)
        java.lang.Class.newInstance(Class.java:261)
        org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
        org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
        org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
        org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
        org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
        org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
        org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
        java.lang.Thread.run(Thread.java:534)

Then ... ? Currently it seems you have to restart Tomcat.

If you get

  java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
        java.lang.ClassLoader.defineClass1(Native Method)
        java.lang.ClassLoader.defineClass(ClassLoader.java:620)
        java.security.SecureClassLoader.defineClass(SecureClassLoader.java:124)
        java.net.URLClassLoader.defineClass(URLClassLoader.java:260)
        java.net.URLClassLoader.access$100(URLClassLoader.java:56)
        java.net.URLClassLoader$1.run(URLClassLoader.java:195)
        java.security.AccessController.doPrivileged(Native Method)
        java.net.URLClassLoader.findClass(URLClassLoader.java:188)
        java.lang.ClassLoader.loadClass(ClassLoader.java:306)
        sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:268)
        java.lang.ClassLoader.loadClass(ClassLoader.java:251)
        org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1270)
        org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1201)
        org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:105)
        org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:148)
        org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:869)
        org.apache.coyote.http11.Http11BaseProtocol$Http11ConnectionHandler.processConnection(Http11BaseProtocol.java:664)
        org.apache.tomcat.util.net.PoolTcpEndpoint.processSocket(PoolTcpEndpoint.java:527)
        org.apache.tomcat.util.net.LeaderFollowerWorkerThread.runIt(LeaderFollowerWorkerThread.java:80)
        org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:684)
        java.lang.Thread.run(Thread.java:595)

Then the problem is with class path. Apparently running the startup.sh script from anywhere else than top level of Tomcat distribution produces the above error.

9.6 Logging and Debugging Tips

In case you add debug prints, the stderr (System.err) output appears to go by default to apache-tomcat-5.5.20/logs/catalina.out

9.7 Debugging libzxidjni.so under jdb and gdb

Debugging the JNI C code would appear to require running java or jdb under gdb and setting break points in the C code. Unfortunately this appears to be particularly tricky. A possible approach is to introduce a sleep(1) in the C code and then use gdb to attach to the java process. Unfortunately even this method does not seem to allow us to set break points.

  export LD_LIBRARY_PATH=zxidjava:$LD_LIBRARY_PATH
  export QUERY_STRING='e=https%3A%2F%2Fidp.symdemo.com%3A8880%2Fidp.xml&l2=+Login+%28SAML20%3APOST%29+&fc=1&fn=prstnt&fq=&fy=&fa=&fm=exact'

N.B. In following "" means Unix shell prompt, "%" gdb prompt, and ">" jdb prompt.

  $ gdb jdb
  % set env LD_LIBRARY_PATH=zxidjava
  % set env QUERY_STRING=e=https%3A%2F%2Fidp.symdemo.com%3A8880%2Fidp.xml&l2=+Login+%28SAML20%3APOST%29+&fc=1&fn=prstnt&fq=&fy=&fa=&fm=exact
  % r zxid
  > stop at zxid:24
  > run
  > next      # or step
  > print cf
  > cont

10 C# (csharp) Support

*** TBD: SWIG based, try make csharpzxid

11 Ruby Support

*** TBD: SWIG based, try make rubyzxid

12 zxid_simple() API

The ZXID library provides two main APIs: the simple and the full. The simple API is meant to get you going with minimal understanding about SAML or SSO. It tries to encapsulate as much control flow as possible.

12.1 Hello World

Consider the follwing "Hello World" CGI example in C ((<tt>zxidhlo.c</tt>
 in the source distribution is an actually usable example program. You should
 also look at its cousins <tt>zxidhlo.php</tt> and zxidhlo.pl)) (the zxid_simple() API is available in all language bindings):

  01 #include <zx/zxid.h>
  02 #define CONF "PATH=/var/zxid/&URL=https://sp1.zxidsp.org:8443/zxid"
  03 void main() {
  04   char* res = zxid_simple(CONF, 0, 255);
  05   switch (res[0]) {
  06   case 'd': /* Logged in case */
  07     my_parse_ldif(res);
  08     my_render_content();
  09     exit(0);
  10   default:
  11     ERR("Unknown zxid_simple() response(%s)", res);
  12   }
  13 }

What happens here:

  1. The CGI script calls zxid_simple() to handle SAML protocol according to the configuration

  2. The last argument with value 255 tells zxid_simple() to automatically handle redirections, login screen and any other protocol interaction needed to make SSO happen.

  3. If zxid_simple() returns, we have either succeeded in SSO or we have failed (all other cases are handled internally by zxid_simple() which calls exit(2) so it never returns).

  4. In the success case, zxid_simple() returns an LDIF entry (as a nul terminated C string) describing the SSO and the attributes received. For example

         dn: idpnid=Pa45XAs2332SDS2asFs,affid=https://idp.demo.com/idp.xml
         objectclass: zxidsession
         affid: https://idp.demo.com/idp.xml
         idpnid: Pa45XAs2332SDS2asFs
         authnctxlevel: password
         sesid: S12aF3Xi4A
         cn: Joe Doe

    where

    dn

    LDAP distinguished name (part of LDIF format). Always first.

    objectclass

    Part of LDIF format.

    affid

    Specifies which IdP was used for SSO

    idpnid

    The federated ID, or pseudonym (IdP assigned NameID)

    authnctxlevel

    Rough indication of how IdP authenticated user

    sesid

    Session ID, as may be stored in cookie or used for file name in the session cache (/var/zxid/ses)

    setcookie

    If this field is not empty and not equal to '-' (single dash), then the controlling appication should take the necessary actions to set the cookie as specified. Namely, the controlling application should create 'Set-Cookie' HTTP response header whose value is the value of the setcookie field. How this is done is environment dependent.

    cookie

    The ZXIDSES (or other as configured) cookie value. For reference.

    cn

    Common Name. This attribute just exemplifies how any additional attributes the IdP may have set will appear. Typically the LDAP attribute names are used.

    The dn line will always be the first. All other lines may appear in any order. String representation of LDIF was chosen as it is easy to parse in most programming languages. ((Some people have advocated JSON representation and it indeed seems
 reasonable. But there is no compelling argument why it would be better
 than LDIF. It clearly is better for JavaScript programs, but it is
 more complicated for most other scripting languages. Keep the feedback coming.))

12.1.1 Configuration Options

The zxid_simple() can be configured in following ways (later ways can override earlier ones).

  1. Built in default configuration

  2. Configuration file, usually /var/zxid/zxid.conf, if any

  3. Configuration string passed as first argument. While configuration string can override all other options (i.e. it is processed last), the PATH specification is parsed before the configuration file is looked up.

Turns out that often the default configuration modified by the configurations string is all you need - you do not need to prepare configuration file.

See section "Configuring and Running" for complete list of configuration options, but generally it is sufficient to specify only a handful:

PATH

Where files are kept and configuration file is found.

URL

The URL of the SP

CDC_URL

The Common Domain URL (optional, if omitted the Common Domain Cookie processing is disabled)

12.1.2 AUTO options (auto flags)

The auto_flags argument allows you to control which operations should be performed automatically and which should be passed to the calling application, see "Gaining More Control" section, below, for full description of this case.

The auto options can be added together.

Table 2:zxid_simple() AUTO options
Dec Hex Symbol Description
1 0x01 ZXID_AUTO_EXIT Call exit(), 0=return "n", even if auto CGI
2 0x02 ZXID_AUTO_REDIR Automatically handle redirects, assume CGI (calls exit(2))
4 0x04 ZXID_AUTO_SOAPC SOAP response handling, content gen
8 0x08 ZXID_AUTO_SOAPH SOAP response handling, header gen
16 0x10 ZXID_AUTO_METAC Metadata response handling, content gen
32 0x20 ZXID_AUTO_METAH Metadata response handling, header gen
64 0x40 ZXID_AUTO_LOGINC IdP select / Login page handling, content gen
128 0x80 ZXID_AUTO_LOGINH IdP select / Login page handling, header gen
256 0x100 ZXID_AUTO_MGMTC Management page handling, content gen
512 0x200 ZXID_AUTO_MGMTH Management page handling, header gen
1024 0x400 ZXID_AUTO_FORMF In idp list and mgmt screen, generate form fields
2048 0x800 ZXID_AUTO_FORMT In idp list and mgmt screen, wrap the output in <form> tag.
4095 0xfff ZXID_AUTO_ALL Enable all automatic CGI behaviour.
4096 0x1000 ZXID_AUTO_DEBUG Enable debugging output to stderr.

If the AUTO_REDIR flag is true, the LOCATION header is sent to stdout and exit(0) may be called, depending on the AUTO_NOEXIT flag.

The SOAP, META, LOGIN, and MGMT flags follow convention:

  HC
  00  No automation. Only action letter is returned ("e"=login, "b"=meta, etc.)
  01  Content, not wrapped in headers, is returned as a string
  10  Headers and content is returned as a string
  11  Headers and content are sent to stdout, CGI style and
      exit(0) may be called, depending on AUTO_EXIT. Otherwise "n" is returned.

Whether exit(0) is called in 11 case depends on ZXID_AUTO_NOEXIT flag.

How much HTML is generated for Login page and Mgmt page in 01 (content only) mode depends on AUTO_PAGE and AUTO_FORM flags

  TF
  00  reserved / nothing is generated
  01  Only form fields (but not form tag itself) are generated.
  10  Complete form is generated
  11  Whole page is generated (best support)

12.1.3 Configuration options for customizing HTML

When whole page is generated, some templating information is taken from the configuration.

IDP_SEL_START

All the HTML before <form> tag. This can include HTML headers and the <body> tag, as well as beginning of the page, allowing for complete color selection, stylesheet embedding, and general branding of the page.

IDP_SEL_NEW_IDP

The HTML fragment to allow login using a new IdP (Auto CoT using IdP URL). Set to empty to hide this possibility.

IDP_SEL_OUR_EID

Message displaying SP Entity ID, in case (technically minded) user needs to know this to establish relationship with an IdP.

IDP_TECH_USER

Technical parameters that user might want to set, and typically would be allowed to set. May be hidden (not user controllable) or visible.

fc

Create federation (AllowCreate flag)

fn

Name ID format

prstnt

Persistent (pseudonym)

trnsnt

Transient, temporary pseudonym

IDP_TECH_SITE

Technical parameters that the site administrator should decide and set. Usually hidden fields:

fq

Affiliation ID (usually empty)

fy

Consent obtained by SP for the federation or SSO

empty

No statement about consent

urn:liberty:consent:obtained

Has been obtained (unspecified way)

urn:liberty:consent:obtained:prior

Obtained prior to present transaction, e.g. user signed terms and conditions of service

urn:liberty:consent:obtained:current:implicit

Consent is implicit in the situation where user came to invoke service

urn:liberty:consent:obtained:current:explicit

Obtained explicitly

urn:liberty:consent:unavailable

Consent can not be obtained

urn:liberty:consent:inapplicable

Obtaining consent is not relevant for the SP or service.

fa

Authentication Context (strength of authentication) needed by the SP

fm

Matching rule for authentication strength (usually empty, IdP decides)

fp

Forbid IdP from interacting with the user (IsPassive flag)

ff

Request reauthentication of user (ForceAuthn flag)

12.2 Gaining More Control

The fully automated interface works well for CGI deployment but probably is not appropriate in more complex situations. You can use zxid_simple() without automation and prevent it from accessing the system layer too directly. Consider

  01 #define CONF "PATH=/var/zxid/&URL=https://sp1.zxidsp.org:8443/zxid"
  02 int main() {
  03   char* res;
  04   res = zxid_simple(CONF, getenv("QUERY_STRING"), 0);
  05   switch (res[0]) {
  06   case 'L': printf("%s", res); exit(0);  /* Redirect */
  07   case 'C': printf("%s", res); exit(0);  /* Content-type + content */
  08   case '<': printf("Content-Type: text/html\r\n\r\n%s", res); exit(0);
  09   case 'n': exit(0);
  10   case 'b': my_send_metadata();
  11   case 'e': my_render_login_screen();
  12   case 'd': /* Logged in case */
  13     my_parse_ldif(res);
  14     my_render_content();
  15     exit(1);
  16   default:
  17     ERR("Unknown zxid_simple() response(%s)", res);
  18   }
  19 }

Here we specify zero as auto_flags, thus all automation is disabled. This means that the res can take more varied shape and the calling application has to be prepared to handle them. The different cases are distinguished by the first character of the res string:

L

Redirection request (L as in Location header). The full contents of the res is the redirection request, ready to be printed to stdout of a CGI. If you want to handle the redirection some other way, you can parse the string to extract the URL and do your thing. This res is only returned if you did not set ZXID_AUTO_REDIR.

Example:

      Location: https://sp1.zxidsp.org:8443/zxid?o=C
C

Content with Content-type header. The res is ready to be printed to the stdout of a CGI, but if you want to handle it some other way, you can parse the res to extract the header and the actual body.

Example:

      CONTENT-TYPE: text/html
      
      <title>Login page</title>
      ...

Example (metadata):

      CONTENT-TYPE: text/xml
      
      <m:EntityDescriptor>
      ...
Less than ("<")

Content without headers. This could be HTML content for login page or metadata XML. To know which (and set content type correctly), you would have to parse the content. This res format is only applicable if you did not specify ZXID_AUTO_CTYPE (but did specify ZXID_AUTO_CONTENT).

n

Do nothing. The operation was somehow handled internally but the exit(2) was not called (e.g. ZXID_AUTO_SOAP was NOT specified). The application should NOT attempt generating any output.

b

Indication that the application should send SP metadata to the client. This res is only returned if you did not set ZXID_AUTO_META.

e

Indication that the application should display the login page. Application may use zxid_idp_list() to render the IdP selection area. This res is only returned if you did not set ZXID_AUTO_CONTENT.

d

Indication that SSO has been completed or that there was an existing valid session in place. The res is an LDIF entry containing attributes that describe the SSO or session. See "Hello World" section for description of the LDIF data.

Usually your application would parse the attributes and then render its application specific content. If you want to render the SSO management form (with logout and defederate buttones), you can call zxid_fed_mgmt().

Asterisk ("*")

Although any unknown letter should be interpreted as an error, we follow convention of prefixing errors with an asterisk ("*").

12.3 Some Generalization and Optimizations

The simplest APIs are easy to use and suitable for CGIs where the program is restarted anew every time. However in situations where the script engine stays alive persistently, it is wasteful to reparse (and reallocate) the configuration every time. Consider following PHP snippet designed to be used with mod_php:

  01 # Put this in the PHP initialization (it only needs to run once)
  02 dl("php_zxid.so");
  03 $conf = "PATH=/var/zxid/&URL=https://sp1.zxidsp.org:8443/zxiddemo.php";
  04 $cf = zxid_new_conf_to_cf($conf);
  05 <?   # For every page that is accessed
  06 $qs = $_SERVER['REQUEST_METHOD'] == 'GET'
  07       ? $_SERVER['QUERY_STRING']
  08       : file_get_contents('php://input');
  09 $res = zxid_simple_cf($cf, -1, $qs, null, 0x1800);
  10 switch (substr($res, 0, 1)) {
  11 case 'L': header($res); exit;  # Redirect
  12 case 'n': exit;   # already handled
  13 case 'b': my_send_metadata();
  14 case 'e': my_render_login_screen();
  15 case 'd': break;  # Logged in case -- fall through
  16 default:  error_log("Unknown zxid_simple() res(%s)", res); exit;
  17 }
  18 # *** Parse the LDIF in $res into a hash of attributes (see zxidhlo.php)
  19 
  20 ?>
  21 <html><title>Protected content, logged in as <$=$attr['cn']$></title>
  22 ...
  23 </html>
  24 <?
  25 function my_render_login_screen()
  26 {
  27 ?>
  28 <html><title>Please Login Using IdP</title>
  29 ...
  30 <?=zxid_idp_select_cf($cf, 0, 0x1800)?>
  31 ...</html>
  32 <? exit; }?>

Notes

  1. Line 4 creates a system-wide configuration object that is later used by the other API calls

  2. On line 9 we call zxid_simple_cf() with the created object. The second and third argument specify a buffer or string that contains the CGI form data to parse. This may come from QUERY_STRING of a GET request or from HTTP body of a POST request, as determined on line 8. The -1 means the length of the data should be determined using strlen(3), i.e. C string nul termination. The auto_flags == 0x1800 enables form tag wrapping and debug prints, but otherwise automation is disabled.

  3. Since automation was disabled, we need to handle several cases of possible outcomes from zxid_simple_cf(), on lines 10-17.

  4. From line 18 onwards we handle the login successful or already logged in case. First we split the LDIF entry into a hash so that we can access the attributes easily (e.g. cn on line 20).

  5. On line 30 we call zxid_idp_list_cf() to create the form for choosing which IdP to use for login (remember that auto_flags == 0xc0 enabled the form wrapper). As can be seen the same configuration object, $cf, is used through out.

12.4 Java Servlet Example Using Tomcat

Consider

  01 import zxidjava.*;
  02 import java.io.*;
  03 import javax.servlet.*;
  04 import javax.servlet.http.*;
  05 public class zxidhlo extends HttpServlet {
  06   static { System.loadLibrary("zxidjni"); }
  07   static final String conf
  08     = "PATH=/var/zxid/&URL=http://sp1.zxidsp.org:8080/zxidservlet/zxidHLO";
  09   public void do_zxid(HttpServletRequest req, HttpServletResponse res, String qs)
  10                       throws ServletException, IOException {
  11     String ret = zxidjni.simple(conf, qs, 0xd54);
  12     switch (ret.charAt(0)) {
  13     case 'L':  /* Redirect: ret == "LOCATION: urlCRLF2" */
  14       res.sendRedirect(ret.substring(10, ret.length() - 4));
  15       return;
  16     case '<':
  17       switch (ret.charAt(1)) {
  18       case 's':  /* <se:  SOAP envelope */
  19       case 'm':  /* <m20: metadata */
  20         res.setContentType("text/xml");
  21         break;
  22       default:
  23         res.setContentType("text/html");
  24       break;
  25       }
  26       res.setContentLength(ret.length());
  27       res.getOutputStream().print(ret);
  28       break;
  29     case 'd': /* Logged in case */
  30       //my_parse_ldif(res);
  31       res.setContentType("text/html");
  32       res.getOutputStream().print(zxidjni.fed_mgmt(conf, 0xd54));
  33       break;
  34     default:
  35       System.err.print("Unknown zxid_simple() response:");
  36       System.err.print(ret);
  37     }
  38   }
  39   public void doGet(HttpServletRequest req, HttpServletResponse res)
  40                     throws ServletException, IOException {
  41     // LECP/ECP PAOS header checks
  42     do_zxid(req, res, req.getQueryString());
  43   }
  44   public void doPost(HttpServletRequest req, HttpServletResponse res)
  45                      throws ServletException, IOException {
  46     String qs;
  47     int len = req.getContentLength();
  48     byte[] b = new byte[len];
  49     int got = req.getInputStream().read(b, 0, len);
  50     qs = new String(b, 0, got);
  51     do_zxid(req, res, qs);
  52   }
  53 }

12.5 Shell Script API

Any Bourne shell (Unix shell) shell script can be converted to a SAML SSO enabled CGI script using zxidsimple(1) helper utility. The program simply wraps the zxid_simple() API function so that the inputs can be provided as command line arguments, or in case of qs as stdin, and the output is returned on stdout.

Synopsis

   zxidsimple -o ldif CONF AUTO_FLAGS <cgi-input

Typical usage (see also zxidhlo.sh):

  CONF="PATH=/var/zxid/&URL=https://sp1.zxidsp.org:8443/zxidhlo.sh"
  ./zxidsimple -o /tmp/zxidhlo.sh.$$ $CONF 4094 || exit;
  IFS="
  "
  res=`cat /tmp/zxidhlo.sh.$$`
  case "$res" in
  dn*)
    for x in $res; do
      case "$x" in
      sesid:*)  SID=${x##*sesid: } ;;
      idpnid:*) NID=${x##*idpnid: } ;;
      cn:*)     CN=${x##*cn: } ;;
      esac
    done
    ;;
    *) echo "ERROR($res)" >>/tmp/hlo.err; exit ;;
  esac

  cat << EOF
  Content-Type: text/html

  <title>ZXID HELLO SP Mgmt</title>
  <h1>ZXID HELLO SP Management (user $CN logged in, session active)</h1>
  <form method=post action="zxidhlo.sh?o=P">
  <input type=hidden name=s value="$SID">
  <input type=submit name=gl value=" Local Logout ">
  <input type=submit name=gr value=" Single Logout (Redir) ">
  </form>
  EOF

The zxidsimple(1) utility will return exit value 1 if it handled a SAML protocol operation (by outputting to stdout whatever was appropriate). The shell script should not do any further processing and just exit.

If the exit value is 0 (success) then SSO has been done. Since the attributes from the SAML assertion are usually interesting, you can capture them to a temporary file using the -o option.

First we split the result of the backtick into a list on (literal) newline. Then we process the list with for loop and look with case for the interesting attributes and capture them into local variables.

Finally the protected content page is output.

12.6 Form Field Naming

The ZXID cgi interface assumes certain hardwired form field names. These are not configurable (and there is no intent to make them configurable). The cgi fields may appear either in query string (GET method) or as POST content (though depending on your programming environment and language, you may need to read the POST data in yourself prior to calling zxid_simple()).

12.6.1 Common Fields

o

Operation. In particular o=P means that form uses POST method.

s

Session ID

RelayState

SAML 2.0 mandated field name for relay state

SAMLart

SAML 2.0 mandated field name for SAML artifact

SAMLResponse

SAML 2.0 mandated field name for SAML response, especially in POST profile

SAMLRequest

SAML 2.0 mandated field name for SAML request

SigAlg

SAML 2.0 mandated field name for signature algorithm in redirect binding

Signature

SAML 2.0 mandated field name for signature in redirect binding

12.6.2 IdP Selection (Login) Screen

u

User (local login)

p

Password (local login)

c

Common Domain Cookie

e

Entity ID (manual entry field)

d

Entity ID (from popup or radio box)

i

Protocol index

l1

Login using artifact profile (same as i=1)

l2

Login using POST profile (same as i=2)

l1EID

Login using specified IdP (artifact profile), same as e=EID&i=1

l2EID

Login using specified IdP (POST profile), same as e=EID&i=2

fc

Allow Create flag

fp

IsPassive flag

ff

Force Authentication flag

fn

NameID format

fq

Affiliation ID

fy

Consent field

fm

Matching rule

fa

Authentication Context Class

fr

Relay State

The IdP selection form (aka Login) screen can be implemented, using the above documented form interface, in many ways as following examples illustrate.

Example IdP Selection Form: Popup menu method

***

Example IdP Selection Form: Separate IdP buttons method

  <form method=post
    action="http://sp1.zxidsp.org:8080/zxidservlet/zxidHLO?o=P">
  <h3>Login Using New IdP</h3>
  <p>IdP URL <input name=e size=80>
      <input type=submit name=l1 value=" Login (A2) ">
      <input type=submit name=l2 value=" Login (P2) "><br>

  <h3>Login Using Known IdP</h3>
  <input type=submit name="l1https://a-idp.liberty-iop.org:8881/idp.xml"
         value=" Login to https://a-idp.liberty-iop.org:8881/idp.xml (A2) ">
  <input type=submit name="l2https://a-idp.liberty-iop.org:8881/idp.xml"
         value=" Login to https://a-idp.liberty-iop.org:8881/idp.xml (P2) ">

  <h3>Technical options</h3>
  <input type=checkbox name=fc value=1 checked> Create federation,
     NID Format: <select name=fn>
                   <option value=prstnt>Persistent
                   <option value=trnsnt>Transient
                   <option value="">(none)
                 </select><br>

  <input type=hidden name=fq value="">
  <input type=hidden name=fy value="">
  <input type=hidden name=fa value="">
  <input type=hidden name=fm value="">
  <input type=hidden name=fp value=0>
  <input type=hidden name=ff value=0>
  </form>

Example IdP Selection Form: IdP links method

***

12.6.3 Single Logout and Federation Management

gl

Local Logout

gr

Single Logout using redirection

gs

Single Logout using SOAP

gt

NameID Managment (redirect)

gu

NameID Management (SOAP)

Example Management Form

  <form method=post action="zxid?o=P">
  <input type=hidden name=s  value="">
  <input type=submit name=gl value=" Local Logout ">
  <input type=submit name=gr value=" Single Logout (Redir) ">
  <input type=submit name=gs value=" Single Logout (SOAP) ">
  <input type=submit name=gt value=" Defederate (Redir) ">
  <input type=submit name=gu value=" Defederate (SOAP) ">
  </form>

13 Integration with Existing Web Sites

Single Sign-On is used to protect some useful resources. ZXID does not have any means of serving these resources, rather a normal web server or application server should do it. ZXID should just concentrate on verifying that a user has valid session, and if not, establishing the session by way of SSO.

13.1 Brief Overview of Control Flow

The SAML 2.0 specifications mandate a wire protocol, and in order to speak the wire protocol, the SP application typically has to follow certain standard sequence of control flow.


Fig-2: Typical control flow of ZXID SP

First a user ((The user is often referred to as "Principal" in
 more technical jargon. Although the human user and web browser are
 distinct entities, we do not stress that separation here. Whatever
 user "does" really will, in protocol, appear as web browser sending
 requests.)) tries to access a web site that acts in SP role. This triggers following sequence of events

  1. User is redirected to URL in a common domain. This is so that we can read the Common Domain Cookie that indicates which IdP the user uses. Alternatively, if you started at https://sp1.zxidsp.org:8443/zxid?o=E, the CDC check is by-passed and flow 2b. happens.

  2. After the CDC check, a Authentication Request (AuthnReq) is generated. The IdP may have been chosen automatically using CDC (2a), or there may have been some user interface interaction (not show in the diagram) to choose the IdP.

  3. User is redirected to the IdP. The redirection carries as a query string a compressed and encoded form of the SAML 2.0 AuthnReq.

  4. Once the IdP has authenticated the user, or observed that there already is a valid IdP session (perhaps from a cookie), the IdP redirects the user back to the SP.

    The AuthnResponse may be carried in this redirection in a number of alternate ways

    1. The redirect contains a special token called artifact. The artifact is a reference to the AuthnResponse and the SP needs to get the actual AuthnResponse by using a SOAP call (the 4bis step).

    2. The "redirect" is actually a HTML page with a form and little JavaScript that causes the form to be automatically posted to the SP. The AuthnResponse is carried as a form field.

  5. After verifying that AuthnResponse indicated a success, the SP establishes a local session for the user (perhaps setting a cookie to indicate this).

    Depending on how the SP to web site integration is done the user is taken to the web site in one of the two ways

    1. Redirect to the content. This time the session is there, therefore the flow passes directly from check session to the web content.

    2. It is also possible to show the content directly without any intervening redirection.

13.2 Redirect Approach to Integration

13.3 Pass-thru Approach to Integration

13.3.1 mod_perl pass-thru

13.3.2 PHP pass-thru

13.3.3 mod_zxid pass-thru

13.4 Proxy Approach to Integration

14 Full Native C API

The generated aspects of the native C API are in c/*-data.h, for example

  c/zx-sa-data.h

Studying this file is very instructive. ((emacs tip: run
 `make tags' and then try hitting M-. while cursor is over a struct
 or function name in <tt>c/zx-sa-data.h</tt> - this makes navigation painless.))

14.1 C Data Structures

From .sg a header (NN-data.h) is generated. This header contains structs that represent the data of the elements. Each element and attribute generates its own node. Even trivial nodes like strings have to be kept this way because the nodes form basis of remembering the ordering of data. This ordering is needed for exclusive XML canonicalization, and thus for signature verification. ((It's unfortunate that
 the XML standards do not make this any easier. Without order
 maintenance requirement, it would be possible to represent trivial
 child elements directly as struct fields. An approach that tried to do
 just this is available from CVS tag GEN_LALR (ca. 29.5.2006).))

Any missing data is represented by NULL pointer.

Any repeating data is kept as a linked list, in reverse order of being seen in the data stream. ((Reverse order is just an
 optimization - or an artifact of simply adding latest element to the
 head of the list. If this bothers you, it's easy enough to reverse the
 list afterwards. Linked list is simple and works well for data whose
 order does not matter much (we use separate pointer for remembering
 the canonicalization order) and where random access is not needed, or
 cardinality is low enough so that simple pointer chasing is efficient
 enough.))

Simple elements and all attributes are represented by simple stri