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.
ZXID project has currently (Jan 2007) five outputs
A C library for supporting SAML 2.0, including federated Single Sign-On (SSO)
A C program that implements a SAML Service Provider (SP) as a CGI script
A Perl module wrapping libzxid. Also zxid.pl, that implements SP in mod_perl environment, is supplied.
A PHP extension that wraps libzxid. Also supplied: zxid.php that implements SP in mod_php environment.
A Java JNI extension that wraps libzxid. Also supplied: zxid.java that implements SP as a CGI script.
You need this if you are
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.
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.
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.
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.
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.
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.
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 make # default Linux. Do `make TARGET=sol8' for Solaris make dir # Creates /var/zxid hierarchy 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...
This software depends on the following packages:
zlib from zlib.net. Generally whatever comes with your distro is sufficient.
openssl-0.9.8d or later. See www.openssl.org. Generally openssl libraries distributed with most Linux distros are sufficient.
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.
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/
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).
gperf from gnu.org (only for build process when generating code)
swig from swig.org (only for build process and only if you want scripting interfaces)
perl from cpan.org (only for build process and only if you want to generate code from .sg)
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
Lasso: http://lasso.entrouvert.org/
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.
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.
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.
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:
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 --$?--
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
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.
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
http://symlabs.com/Products/SFIAM.html who have a free download of commercial product
Lasso: http://lasso.entrouvert.org/
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.
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.
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).
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.
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.
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
/var/zxid/zxid.conf
for figuring out its parameters. If this file is not present, built-in
default configuration is used (see zxidconf.h).
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
Configure web server (see your web server documentation)
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.
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.
Configure ZXID, including signing certificate and CoT with peer metadata
generate or acquire certificate
Obtain peer metadata (from their well known location) or enable Instant CoT feature.
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.
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
Directory that holds various certificates. The certificates have hardwired names that are not configurable.
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.
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).
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 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.
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.
Directory that holds metadata of the Circle of Trust (CoT) partners. If Instant CoT is enabled, this directory needs to be writable at run time.
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
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
are not disjoint
(it is not very well specified in any of the standards how they
interact or how wide their uniqueness properties are).
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.
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
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
Plain text logging
Signed plain text logging using either RSA-SHA1 or DSA-SHA1
Symmetrically encrypted logging using either 3DES or AES
Asymmetrically encrypted logging using RSA (or DSA?)
Signed and symmetrically encrypted logging
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
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.
PlainPlain: not signed and not encrypted
RSA-SHA1 signed (x = any encryption)
DSA-SHA1 signed
SHA1 check-summed, but not signed (SSSS is the checksum)
Asymmetrically AES encrypted (x = any signing method)
Asymmetrically 3DES encrypted
Symmetrically AES encrypted (theoretical: how to safeguard the key?)
Symmetrically 3DES encrypted (theoretical: how to safeguard the key?)
Experimental arrangements.
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.
Safe base64 encoded log line signature blob. If no signature, this is a dash ("-").
Our time stamp, format YYYYMMDD-HHMMSS.TTT where TTT are the milliseconds. The time is always in GMT (UTC, Zulutime).
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).
The IP address and the port number of the other end point (usually client, but could be spoofed, caveat emptor).
The SHA1 name of the entity (succinct entity ID without the equals sign).
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.
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.
IdP assigned NameID relating to the message, if any. If the NameID is encrypted and can not be decrypted, then placeholder "-encnid-" is used.
Signature validation codes
Capital Oh (not zero). All relevant signatures validate (generally assertion)
Unsupported or bad signature or message digest algorithm
Checksum of XML DSIG does not validate
The RSA layer of the signature does not validate
No signature detected.
Issuer metadata not found (or not in CoT, or corrupt metadata).
Assertion validity error (e.g. not in time range or wrong audience)
Operation failed or faulted by error code (low level protocol ok)
Extended signature validation code (generally failure)
Experimental signature validation code (generally failure)
Result of the operation.
Operation was success
Operation failed because client did not provide valid input
Operation failed due to server side error
Operation failed due to policy or permissions issue
Temporary error, client was encouraged to retry
Metadata related error (no metadata or parse error in metadata)
Redirect or recredential. Client was encouraged to retry.
Way point message. Neither success nor failure.
Extended result (generally failure)
Experimental result (generally failure)
The documented operation
Federation and SSO request succeeded, new federation was created.
SSO using federated ID was performed
SSO using temporary NameID was performed
Single Logout was completed
Defederation was performed
Server configuration (/var/zxid/zxid.conf) is bad
No metadata found after options exhausted (cache, fetch from net)
Metadata parsing error
XML parsing error in protocol
SAML call failed (often SOAP call)
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).
Artifact resolution request sent with SOAP (1)
Redirection with Authentication Request
Local Logout (1)
Redirection with Single Logout Request
Redirection with Manage NameID Request for changing NameID
Redirection with Manage NameID Request for defederation
Single Logout Request SOAP call made
Manage NameID Request for changing NameID SOAP call
Manage NameID Request for defederation SOAP call
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).
IdP Selection screen is shown (2)
Management screen is shown (2)
Logged in (by SSO or session). Show protected content. arg is sid. (1)
SP Command Dispatch (received POST or redir) (2)
My metadata was served to requester on the net (1)
Getting metadata from net (2)
Got metadata from net (1)
Unknown CGI options (0, but not implemented yet)
Operation dependent one most relevant parameter. Dash ("-") if none.
Operation dependent free-form data. May contain spaces. Dash ("-") if none.
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):
no signing (Px)
sha1 MD only (Sx)
RSA-SHA1 (Rx)
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):
no encryption (xP)
RSA-AES (xA)
RSA-3DES (xT)
Symmetric AES (xB)
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
If encrypt, zip the raw log line
If sign, compute the signature (over zipped version if applicable)
Prepend signature blob to log line. If encrypting, the signature is embedded in binary form, otherwise it is embedded in safe-base64 form.
If encrypt, perform the encryption.
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.
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
16 bytes of nonce. This is used as initialization vector
for AES or 3DES cipher operated in CBC mode.
Bigendian integer representing signature length in bytes.
0 means none. Negative values reserved for future use.
The signature in binary
Bigendian integer representing encrypted session key
length in bytes. Negative values are reserved for future use.
RSA encrypted session key in binary
Ciphertext from the symmetric cipher, including nonce.
In RSA operations RSA_PKCS1_OAEP_PADDING padding is used (PKCS #1 v2.0).
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.
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.
The ZXID session system serves three purposes:
Remember whether user has logged in. The session ID is carried either in a cookie or aspart of the URL.
Make it possible to perform Single Logout (SLO) and certain federation management tasks.
Remember the service end points (EPRs) that were either
supplied as bootstrap attributes in the SSO assertion, or
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 | +-- .mni Information needed by Name ID management | `-- log/ Log files, pid files, and the like
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
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.
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, extracted during SSO
Filesystem path to the SSO assertion.
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:
NameID Format
IdP entity ID that qualifies the NameID (namespace if you like). This usually corresponds to the NameQualifier of
SP entity or affilitation ID (optionally) sent by IdP. This further qualifies the namespace of the Name ID.
NameID of the account
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.
make cleaner make dep ENA_GEN=1 make make all ENA_GEN=1
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.
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
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.
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.
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.
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.
Without libcurl, zxid can not act as a SOAP client. This has a few consequences
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.
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.
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.
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.
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.
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.
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 |
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.
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.
***
There are some, yet unspecified, compilation or linking errors that manifest on 64 bit or mixed 32 and 64 bit Linux platforms. Current suggested workaround is to compile 32 bit only.
Linking error that has been reported a lot on the Google for other software packages:
/usr/lib/libc_nonshared.a(elf-init.oS): In function `__libc_csu_init':
(.text+0x2a): undefined reference to `__init_array_start'
/usr/bin/ld: zxidjava/libzxidjni.so: hidden symbol `__fini_array_end' isn't defined
/usr/bin/ld: final link failed: Nonrepresentable section on output
This seems to be either glibc version related or linker configuration related (or compiler version?). See
http://www-gatago.com/gnu/gcc/help/43379636.html
http://lists.debian.org/debian-68k/2006/07/msg00017.html
The verdict seems to be that somewhere in glibc-2.3 development it was broken by design and nobody cared to provide any viable fix short of upgrading your binutils. Seems to be Ulrich Drepper at it again, turning symbols hidden in teeny version number upgrade.
A symptom of this is if this command gives the follwing output
ld --verbose | egrep '__init_array_(start|end)'
PROVIDE_HIDDEN (__init_array_start = .);
PROVIDE_HIDDEN (__init_array_end = .);
You can manually attempt to link with
gcc -nostdlib -o zxidjava/libzxidjni.so -shared --export-all-symbols -Wl,-whole-archive -Wl,--allow-multiple-definition zxidjava/zxid_wrap.o -L. -lzxid -lpthread -L/usr/local/lib -L/usr/local/ssl/lib -lcrypto -lcurl -lz
The trick is to supply the -nostdlib flag.
ZXID project considers this to be glibc or binutils bug and will not investigate further. Either use the workaround link line or upgrade binutils, glibc, or both and complain to glibc authors or binutils authors.
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.
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"
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.
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).
perl CGI example: zxid.pl
using with mod_perl
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.
Net::SAML - The high level interfaces for Single Sign-On (SSO)
Net::SAML::Raw - Low level assertion and protocol manipulation interfaces
Net::SAML::Metadata - Low level metadata manipulation interfaces
Net::WSF - The high level interfaces for Web Services Frameworks (WSF)
Net::WSF::Raw - The low level interfaces for WSF variants
Net::WSF::WSC - The high level interfaces for Web Services Clients
Net::WSF::WSC:Raw
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.
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
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.
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.
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.
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).
See Apache recipe for how to compile Apache to support mod_php.
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 happens, 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?.
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.
TBD using SWIG
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.
The resulting library is called libzxidjni.jnilib
You may need to supply additional arguments to java command:
java -classpath .:zxidjava -Djava.library.path=zxidjava zxid
On Mac, it seems export LD_LIBRARY_PATH=zxidjava is not needed.
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.
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.
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
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.
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
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
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.
Consider the follwing "Hello World" CGI example in C
(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:
The CGI script calls zxid_simple() to handle SAML protocol according to the configuration
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.
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).
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
LDAP distinguished name (part of LDIF format). Always first.
Part of LDIF format.
Specifies which IdP was used for SSO
The federated ID, or pseudonym (IdP assigned NameID)
Rough indication of how IdP authenticated user
Session ID, as may be stored in cookie or used for file name in the session cache (/var/zxid/ses)
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.
The zxid_simple() can be configured in following ways (later ways can override earlier ones).
Built in default configuration
Configuration file, usually /var/zxid/zxid.conf, if any
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:
Where files are kept and configuration file is found.
The URL of the SP
The Common Domain URL (optional, if omitted the Common Domain Cookie processing is disabled)
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 |
| 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)
When whole page is generated, some templating information is taken from the configuration.
All the HTML before
The HTML fragment to allow login using a new IdP (Auto CoT using IdP URL). Set to empty to hide this possibility.
Message displaying SP Entity ID, in case (technically minded) user needs to know this to establish relationship with an IdP.
Technical parameters that user might want to set, and typically would be allowed to set. May be hidden (not user controllable) or visible.
Create federation (AllowCreate flag)
Name ID format
Persistent (pseudonym)
Transient, temporary pseudonym
Technical parameters that the site administrator should decide and set. Usually hidden fields:
Affiliation ID (usually empty)
Consent obtained by SP for the federation or SSO
No statement about consent
Has been obtained (unspecified way)
Obtained prior to present transaction, e.g. user signed terms and conditions of service
Consent is implicit in the situation where user came to invoke service
Obtained explicitly
Consent can not be obtained
Obtaining consent is not relevant for the SP or service.
Authentication Context (strength of authentication) needed by the SP
Matching rule for authentication strength (usually empty, IdP decides)
Forbid IdP from interacting with the user (IsPassive flag)
Request reauthentication of user (ForceAuthn flag)
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:
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
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>
...
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).
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.
Indication that the application should send SP metadata to the client. This res is only returned if you did not set ZXID_AUTO_META.
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.
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().
Although any unknown letter should be interpreted as an error, we follow convention of prefixing errors with an asterisk ("*").
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
Line 4 creates a system-wide configuration object that is later used by the other API calls
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.
Since automation was disabled, we need to handle several cases of possible outcomes from zxid_simple_cf(), on lines 10-17.
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).
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.
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 }
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.
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()).
Operation. In particular o=P means that form uses POST method.
Session ID
SAML 2.0 mandated field name for relay state
SAML 2.0 mandated field name for SAML artifact
SAML 2.0 mandated field name for SAML response, especially in POST profile
SAML 2.0 mandated field name for SAML request
SAML 2.0 mandated field name for signature algorithm in redirect binding
SAML 2.0 mandated field name for signature in redirect binding
User (local login)
Password (local login)
Common Domain Cookie
Entity ID (manual entry field)
Entity ID (from popup or radio box)
Protocol index
Login using artifact profile (same as i=1)
Login using POST profile (same as i=2)
Login using specified IdP (artifact profile), same as e=EID&i=1
Login using specified IdP (POST profile), same as e=EID&i=2
Allow Create flag
IsPassive flag
Force Authentication flag
NameID format
Affiliation ID
Consent field
Matching rule
Authentication Context Class
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
***
Local Logout
Single Logout using redirection
Single Logout using SOAP
NameID Managment (redirect)
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>
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.
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
tries to access a web site that acts in SP role. This
triggers following sequence of events
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.
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.
User is redirected to the IdP. The redirection carries as a query string a compressed and encoded form of the SAML 2.0 AuthnReq.
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
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).
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.
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
Redirect to the content. This time the session is there, therefore the flow passes directly from check session to the web content.
It is also possible to show the content directly without any intervening redirection.
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.
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.
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.
Simple elements and all attributes are represented by simple string node (even if they are booleans or integers).
Example
Consider following XML
<ds:Signature>
<ds:SignedInfo>
<ds:CanonicalizationMethod
Algorithm="http://w3.org/xml-exc-c14n#"/>
<ds:SignatureMethod
Algorithm="http://w3.org/xmldsig#rsa-sha1"/>
<ds:Reference
URI="#RrcrNwFIw6n">
<ds:Transforms>
<ds:Transform
Algorithm="http://w3.org/xml-exc-c14n#"/>
<ds:Transform
Algorithm="http://w3.org/xmldsig#env-sig"/></>
<ds:DigestMethod
Algorithm="http://w3.org/xmldsig#sha1"/>
<ds:DigestValue>lNIzVMrp8CwTE=</></></>
<ds:SignatureValue>
GeMp7LS...vnjn8=</></>
Decoding would produce the data structure in Fig-3. You should also look at c/zx-sa-data.h to see the structs involved in this example.

Fig-3: Typical data structure produced by decode.
There are two pointer systems at play here. The black solid arrows
depict the logical structure of the XML document. For each child
element there is a struct field that simply points to the child. If
there are multiple occurrences of the child, as in
sig->SignedInfo->Reference->Transforms->Transform, the children are
kept in a linked list connected by gg.g.n (next) fields.
The wide order structure, depicted by red dashed arrows, is maintained using gg.kids and gg.g.wo fields. For example sig->SignedInfo->Reference->Transforms keeps its kids, the zx_ds_Transform objects, in the original order hanging from the kids and linked with the wo field. As can be seen, the order kept with wo fields can be dif