Jun 20, 2009

How to trust Country Signing Certificates

I've collected a number of country signing (public key) certificates of different ePassport issuing countries who have put their certificates online. Most of these were brought to my attention by Google's alert service.

The global ICAO PKI for signing ePassports is actually a forrest of many national PKIs. And each national PKI is only 2 levels deep (depending on where you start counting):
  • CSCA: Country Signing Certificate Authority
  • DSCA: Document Signing Certificate Authority
  • AA: Active Authentication "certificate" (which really is not part of the PKI, as this is not a certificate but a raw public key, signed implicitly in the security document of the ePassport)
All of this becomes more complex now that we are moving towards Extended Access Control, but the fact remains that the lack of a central trusted CA makes it difficult to bootstrap trust. ICAO has proposed two alternatives:
  • The central ICAO Public Key Directory (PKD)
  • Country cross signing of CSCs
The first alternative is really an online facility which issues claims about (document) signers. The usual drawbacks apply: single point of failure, cost of maintaining infrastructure, etc. The second alternative involves having each country (or at least as many countries as possible) sign each other's certificates.

Is it possible to have a central CA instead? Some of the government Web sites where I (or rather, Google) found the CSCA certificates are protected using SSL, at least the Dutch site is (yes, I know, the certificate has expired, but I downloaded the CSCA certificate before the expiration date of the server certificate). I could have recorded the SSL transaction while downloading that CSCA certificate and I could have made that part of the CSCA certificate itself. Unfortunately, the commercial CA (in this case Verisign) which signed the server key doesn't make claims about the validity of certificate files hosted at protected servers. Sometimes the Web is just not semantic enough.

Update (July 2009): The certificate of bprbzk.nl was renewed.

Jun 19, 2009

Intermediate certificates in my local trust store

The trust store used by my Firefox browser (on Windows) contains a lot of root CA certificates, and also some intermediate CA certificates (maybe I imported them, maybe my sysadmin did, maybe they came with my browser, but they're there). The root CA certificates are self-signed, the intermediate CA certificates are signed using another certificate from my trust store.

Other browser's trust stores that I checked: curl's (usually located at /usr/share/curl/curl-ca-bundle.crt) and Java's (usually located at $JRE/lib/security/cacerts and accessed with keytool -keystore cacerts -storepass changeit -list) do not contain intermediate certificates (at least not the one I'm using below). They do contain the usual list of root certificates.

My Web server's SSL certificate is signed using one of those intermediate certificates. Here's what my chain looks like:
  • GTE Cybertrust Global Root is signed by GTE Cybertrust Global Root (I saved this certificate to a file called global.crt)
  • Cybertrust Educational CA is signed by GTE Cybertrust Global Root (I saved this certificate to a file called educational.crt)
  • My server's certificate is signed by Cybertrust Educational CA (I saved this certificate to a file called myserver.crt)
To verify the signature by hand using openssl I can use:
openssl verify -CAfile global.crt -untrusted educational.crt myserver.crt
When I first installed the certificate for my Web server (an Apache httpd 2.0 server), I merely configured my own certificate (and corresponding private key):
SSLCertificateFile myserver.crt
This worked fine in my Firefox, which I used to test this server.

But curl refuses (unless I use the --insecure or the --cacert option with a concatenation of the two Cybertrust certificates). Apparently the Web server needs to send the intermediate CA certificate as well (and possibly also the root CA certificate). Here's the correct setup:
SSLCertificateFile myserver.crt
SSLCertificateChainFile educational.crt
SSLCACertificateFile global.crt
While having intermediate certificates in you local trust store doesn't make you less secure, if you're testing your Web server setup it's a good idea to also test it with a minimal trust store. In fact, I found that the best way to test your server's SSL settings is using openssl:
openssl s_client -host myserver.com -port 443
This doesn't use a trust store at all. It shows the certificate chain and gives either an error 20 (unable to get local issuer certificate, in case the server only send the server and intermediate certificates), or an error 19 (self signed certificate in chain, in case the server also sent the root certificate). (Both errors are expected, other errors indicate something may be wrong.)

Jun 17, 2009

OpenID with OpenASelect - Part 4

Finally, here's the oa.xml (yes, it's big, I know):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE oa-server
[
  <!ENTITY server "ldap://ldapserver:389">
  <!ENTITY base "OU=Institute,DC=corporate,DC=domain,DC=com">
  <!ENTITY user "user@domain.com">
  <!ENTITY password "changeme">
]>

<oa-server>


  <!-- Server Configuration -->

  <server id="openid.domain.com" friendlyname="Institute OpenID Server">
   <organization id="Institute OpenID" friendlyname="Institute OpenID" />
  </server>


  <!-- Engine Configuration -->

  <crypto>
   <message_digest algorithm="SHA1" />
   <random_generator algorithm="SHA1PRNG" />
  </crypto>

  <sessionfactory class="com.alfaariss.oa.engine.session.memory.MemorySessionFactory">
   <expire>900</expire>
    <interval>60</interval>
    <max>100</max>
  </sessionfactory>

  <tgtfactory class="com.alfaariss.oa.engine.tgt.memory.MemoryTGTFactory">
   <expire>3600</expire>
    <interval>60</interval>
    <max>100</max>
  </tgtfactory>


  <!-- User Provisioning -->

  <userfactory class="com.alfaariss.oa.engine.user.provisioning.ProvisioningFactory">
   <main>
    <translator>standard</translator>
   </main>
   <translator id="standard" class="com.alfaariss.oa.engine.user.provisioning.translator.standard.StandardTranslator">
    <main>
      <externalstorage>ext_jndi</externalstorage>
      <profile>jndi</profile>
    </main>
    <profile id="jndi" class="com.alfaariss.oa.engine.user.provisioning.translator.standard.StandardProfile">
      <account>
       <enabled default="true" field="userPrincipalName" converter="exist" />
       <authentication>
        <method id="JNDIPassword">
          <registered default="true" />
        </method>
       </authentication>
      </account>
      <convertermanager>
       <converter id="exist" class="com.alfaariss.oa.engine.user.provisioning.translator.standard.converter.exist.ExistConverter" />
      </convertermanager>
    </profile>
   </translator>

   <storagemanager>
    <storage id="ext_jndi" class=".external.jndi.JNDIExternalStorage">
      <resource>
       <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
       <url>&server;</url>
       <ssl>false</ssl>
       <security_principal>
        <dn>&user;</dn>
        <password>&password;</password>
       </security_principal>
       <dn>
        <base>&base;</base>
        <user>userPrincipalName</user>
       </dn>
      </resource>
    </storage>
   </storagemanager>
  </userfactory>

  <attributegatherer id="ax_attribgather" friendlyname="Gatherer" enabled="true">
   <!-- AX axschema.org -->
   <processor id="axschemaorg_jndi" friendlyname="JNDI axschema.org AX Attribute Gatherer"
    class="com.alfaariss.oa.engine.attribute.gather.processor.jndi.JNDIGatherer">
    <resource>
      <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
      <url>&server;</url>
      <ssl>false</ssl>
      <security_principal>
       <dn>&user;</dn>
       <password>&password;</password>
      </security_principal>
      <dn>
       <base>&base;</base>
       <user>userPrincipalName</user>
      </dn>
    </resource>

    <gather>
      <attribute name="c" />
      <attribute name="company" />
      <attribute name="description" />
      <attribute name="givenName" />
      <attribute name="l" />
      <attribute name="mail" />
      <attribute name="mobile" />
      <attribute name="name" />
      <attribute name="postOfficeBox" />
      <attribute name="postalCode" />
      <attribute name="sAMAccountName" />
      <attribute name="sn" />
      <attribute name="telephoneNumber" />
      <attribute name="title" />
      <attribute name="wWWHomePage" />
    </gather>
    <mapper>
      <map int="http://axschema.org/namePerson/friendly" ext="sAMAccountName" />
      <map int="http://axschema.org/namePerson" ext="name" />
      <map int="http://axschema.org/namePerson/prefix" ext="title" />
      <map int="http://axschema.org/namePerson/first" ext="givenName" />
      <map int="http://axschema.org/namePerson/last" ext="sn" />
      <map int="http://axschema.org/company/name" ext="company" />
      <map int="http://axschema.org/company/title" ext="description" />
      <map int="http://axschema.org/contact/phone/default" ext="telephoneNumber" />
      <map int="http://axschema.org/contact/phone/cell" ext="mobile" />

      <map int="http://axschema.org/contact/postalAddress/business" ext="postOfficeBox" />
      <map int="http://axschema.org/contact/city/business" ext="l" />
      <map int="http://axschema.org/contact/country/business" ext="c" />
      <map int="http://axschema.org/contact/postalCode/business" ext="postalCode" />
      <map int="http://axschema.org/contact/email" ext="mail" />
      <map int="http://axschema.org/contact/web/default" ext="wWWHomePage" />
    </mapper>
   </processor>

   <!-- AX openid.net/schema -->
   <processor id="openidnet_jndi" friendlyname="JNDI axschema.org AX Attribute Gatherer"
    class="com.alfaariss.oa.engine.attribute.gather.processor.jndi.JNDIGatherer">
    <resource>
      <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
      <url>&server;</url>
      <ssl>false</ssl>
      <security_principal>
       <dn>&user;</dn>
       <password>&password;</password>
      </security_principal>
      <dn>
       <base>&base;</base>
       <user>userPrincipalName</user>
      </dn>
    </resource>

    <gather>
      <attribute name="c" />
      <attribute name="company" />
      <attribute name="description" />
      <attribute name="givenName" />
      <attribute name="l" />
      <attribute name="mail" />
      <attribute name="mobile" />
      <attribute name="name" />
      <attribute name="postOfficeBox" />
      <attribute name="postalCode" />
      <attribute name="sAMAccountName" />
      <attribute name="sn" />
      <attribute name="telephoneNumber" />
      <attribute name="title" />
      <attribute name="wWWHomePage" />
    </gather>
    <mapper>
      <map int="http://openid.net/schema/namePerson/prefix" ext="title" />
      <map int="http://openid.net/schema/namePerson/first" ext="givenName" />
      <map int="http://openid.net/schema/namePerson/last" ext="sn" />
      <map int="http://openid.net/schema/namePerson/friendly" ext="sAMAccountName" />
      <map int="http://openid.net/schema/contact/phone/default" ext="telephoneNumber" />
      <map int="http://openid.net/schema/contact/postaladdress/business" ext="postOfficeBox" />
      <map int="http://openid.net/schema/contact/city/business" ext="l" />
      <map int="http://openid.net/schema/contact/country/business" ext="c" />
      <map int="http://openid.net/schema/contact/postalcode/business" ext="postalCode" />
      <map int="http://openid.net/schema/contact/internet/email" ext="mail" />
      <map int="http://openid.net/schema/contact/web/default" ext="wWWHomepage" />
      <map int="http://openid.net/schema/company/name" ext="company" />
      <map int="http://openid.net/schema/company/title" ext="description" />
    </mapper>
   </processor>

   <!-- AX schema.openid.net -->
   <processor id="schemaopenidnet_jndi" friendlyname="JNDI axschema.org AX Attribute Gatherer"
    class="com.alfaariss.oa.engine.attribute.gather.processor.jndi.JNDIGatherer">
    <resource>
      <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
      <url>&server;</url>
      <ssl>false</ssl>
      <security_principal>
       <dn>&user;</dn>
       <password>&password;</password>
      </security_principal>
      <dn>
       <base>&base;</base>
       <user>userPrincipalName</user>
      </dn>
    </resource>

    <gather>
      <attribute name="c" />
      <attribute name="company" />
      <attribute name="description" />
      <attribute name="givenName" />
      <attribute name="l" />
      <attribute name="mail" />
      <attribute name="mobile" />
      <attribute name="name" />
      <attribute name="postOfficeBox" />
      <attribute name="postalCode" />
      <attribute name="sAMAccountName" />
      <attribute name="sn" />
      <attribute name="telephoneNumber" />
      <attribute name="title" />
      <attribute name="wWWHomePage" />
    </gather>

    <mapper>
      <map int="http://schema.openid.net/namePerson/prefix" ext="title" />
      <map int="http://schema.openid.net/namePerson/first" ext="givenName" />
      <map int="http://schema.openid.net/namePerson/last" ext="sn" />
      <map int="http://schema.openid.net/namePerson/friendly" ext="sAMAccountName" />
      <map int="http://schema.openid.net/contact/phone/default" ext="telephoneNumber" />
      <map int="http://schema.openid.net/contact/postaladdress/business" ext="postOfficeBox" />
      <map int="http://schema.openid.net/contact/city/business" ext="l" />
      <map int="http://schema.openid.net/contact/country/business" ext="c" />
      <map int="http://schema.openid.net/contact/postalcode/business" ext="postalCode" />
      <map int="http://schema.openid.net/contact/internet/email" ext="mail" />
      <map int="http://schema.openid.net/contact/web/default" ext="wWWHomepage" />
      <map int="http://schema.openid.net/company/name" ext="company" />
      <map int="http://schema.openid.net/company/title" ext="description" />
    </mapper>
   </processor>

   <!-- AX verify.sxip.com -->
   <processor id="sxip_jndi" friendlyname="JNDI axschema.org AX Attribute Gatherer"
    class="com.alfaariss.oa.engine.attribute.gather.processor.jndi.JNDIGatherer">
    <resource>
      <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
      <url>&server;</url>
      <ssl>false</ssl>
      <security_principal>
       <dn>&user;</dn>
       <password>&password;</password>
      </security_principal>
      <dn>
       <base>&base;</base>
       <user>userPrincipalName</user>
      </dn>
    </resource>

    <gather>
      <attribute name="mail" />
    </gather>
    <mapper>
      <map int="http://verify.sxip.com/schema/verifiedEmail" ext="mail" />
    </mapper>
   </processor>

   <!-- SREG -->
   <processor id="sreg_jndi" friendlyname="JNDI SREG Attribute Gatherer"
    class="com.alfaariss.oa.engine.attribute.gather.processor.jndi.JNDIGatherer">
    <resource>
      <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
      <url>&server;</url>
      <ssl>false</ssl>
      <security_principal>
       <dn>&user;</dn>
       <password>&password;</password>
      </security_principal>
      <dn>
       <base>&base;</base>
       <user>userPrincipalName</user>
      </dn>
    </resource>
    <gather>
      <attribute name="c" />
      <attribute name="mail" />
      <attribute name="name" />
      <attribute name="postalCode" />
      <attribute name="sAMAccountName" />
    </gather>
    <mapper>
      <map int="nickname" ext="sAMAccountName" />
      <map int="email" ext="mail" />
      <map int="fullname" ext="name" />
      <map int="postcode" ext="postalCode" />
      <map int="country" ext="c" />
    </mapper>
   </processor>
  </attributegatherer>

  <attributerelease class="com.alfaariss.oa.engine.attribute.release.configuration.ConfigurationFactory">
   <policy id="releasepolicy.1" friendlyname="Release Policy" enabled="true">
    <attribute name="*" />
   </policy>
  </attributerelease>


  <!-- Business Logic -->

  <requestorpoolfactory class="com.alfaariss.oa.engine.requestor.configuration.ConfigurationFactory">
   <pool id="requestorpool.1" friendlyname="OpenID Requestor Pool" enabled="true">
    <authentication forced="false">
      <profile id="authentication.openid" />
    </authentication>
    <attributerelease policy="releasepolicy.1" />
    <requestors type="sp">
      <requestor id="openid" friendlyname="OpenID requestor" enabled="true" />
    </requestors>
   </pool>
  </requestorpoolfactory>

  <authentication class="com.alfaariss.oa.engine.authentication.configuration.ConfigurationFactory">
   <profile id="authentication.openid" friendlyname="Login" enabled="true">
    <method id="JNDIPassword" />
   </profile>
  </authentication>


  <!-- Web SSO -->

  <websso single_sign_on="true">
   <cookie domain="domain.com" />
   <view>
    <profile_selection path="/ui/sso/select.jsp" />
    <user_info path="/ui/sso/user_info.jsp" />
    <logged_out path="/ui/sso/logged_out.jsp" />
   </view>
   <authentication always_show_select_form="false">
    <methods>

      <method id="JNDIPassword" friendlyname="Password Authentication"
           class="com.alfaariss.oa.authentication.password.PasswordAuthenticationMethod">
       <template path="/ui/sso/authn/password/password.jsp" />
       <retries>3</retries>
       <password_handler class="com.alfaariss.oa.authentication.password.jndi.JNDIPasswordHandler">
        <resource realm="@domain.com">
          <full_uid>true</full_uid>
          <driver>com.sun.jndi.ldap.LdapCtxFactory</driver>
          <url>&server;</url>
          <ssl>false</ssl>
          <security_principal>
           <dn>&user;</dn>
           <password>&password;</password>
          </security_principal>
          <dn>
           <base>&base;</base>
           <user>userPrincipalName</user>
          </dn>
        </resource>
       </password_handler>
      </method>

    </methods>
   </authentication>
  </websso>


  <!-- IdP Profiles -->

  <profiles>

   <profile id="openid" class="com.alfaariss.oa.profile.openid.OpenIDProcessor">
    <privatestore>memory</privatestore>
    <sharedstore>memory</sharedstore>
    <usermapping>simple</usermapping>
    <interval>86400</interval>
    <expirytime>1800</expirytime>
    <endpoint>https://openid.domain.com/openaselect/profiles/openid/</endpoint>
    <confirmationpage>/ui/profiles/openid/authz_consumer.jsp</confirmationpage>
    <xrdsdocument>/ui/users/xrds/xrds.jsp</xrdsdocument>
    <userdocument>/ui/users/user.jsp</userdocument>
    <error>
      <jsp path="/ui/profiles/openid/error.jsp" />
    </error>
    <websso>
      <path>/sso/web</path>
    </websso>
    <associationstores>
      <associationstore id="memory" class="org.openid4java.server.InMemoryServerAssociationStore"/>
    </associationstores>
    <mappings>
      <mapping id="simple" class="com.alfaariss.oa.profile.openid.mapping.SimpleMapping">
       <config>
        <idtemplate>https://openid.domain.com/[username]</idtemplate>
       </config>
      </mapping>
    </mappings>
    <extensions>
      <extension id="http://openid.net/extensions/sreg/1.0"
      class="com.alfaariss.oa.profile.openid.extension.sreg.SimpleRegistrationProcessor" param_signing="true" enabled="true" />
      <extension id="http://openid.net/extensions/sreg/1.1"
      class="com.alfaariss.oa.profile.openid.extension.sreg.SimpleRegistrationProcessor" param_signing="true" enabled="true" />
      <extension id='http://openid.net/srv/ax/1.0'
      class="com.alfaariss.oa.profile.openid.extension.ax.AttributeExchangeProcessor" param_signing="true" enabled="true" />
    </extensions>
   </profile>

  </profiles>


  <!-- Helpers -->

  <helpers>
   <helper id="stylesheet" enabled="true"
    class="com.alfaariss.oa.helper.stylesheet.StyleSheetHelper">
    <default location="http://openid.domain.com/css/style.css" />
   </helper>
  </helpers>

</oa-server>

Jun 1, 2009

OpenID with OpenASelect - Part 3


Now that my OpenASelect server is running it's time to make it a bit more attractive and robust. Apart from creating flashy JSPs based on our corporate CSS style this means:
  • Make the user URL as short as possible, something like openid.domain.com/username
  • Better yet, instead of the full User Principal Name (UPN) (which includes as a domain @domain.com) I want users to be able to use firstname.lastname
  • Allow users to also use firstname.lastname (without the domain) when they use openid.domain.com as URL (the OAS server asks the user to enter both the user name and the password in that case)
  • Secure the server with a certificate (our authentication is password based, after all)
Most of these issues can be resolved by placing a regular httpd server in front of the Tomcat server (using mod_proxy). The advantage is that mod_rewrite can then be used to allow user to be sloppy with their UPN in omnidirectional identifiers.

The regular httpd server accepts connections on port 80 and proxies these to the Tomcat server which has an AJP connector on port 8009:
ProxyPassReverse /openaselect/ ajp://localhost:8009/openaselect/
ProxyPass /openaselect/ ajp://localhost:8009/openaselect/

Clients can remain unaware of the fact that pages under openaselect/ are actually served by the Tomcat server and not by the httpd server they are connected to. On top of that I used mod_rewrite to get prettier URLs:
RewriteRule ^/([A-Za-z0-9]+\.[A-Za-z0-9]+)$ http://openid.novay.nl/openaselect/profiles/openid/users/$1@domain.com [P,L]
The rewriting trick works well for omnidirectional identifiers in which the username is part of the OpenID URL. When the user merely enters http://openid.novay.nl at an RP and the actual OpenID URL is established through discovery things get a bit more complex. The rewriting needs to be done at a slightly deeper level. The configuration file links to Java classes for many of the sub-processes. In my oa.xml most of these classes are standard Alfa & Ariss classes, for example I used a StandardTranslator inside the user provisioning process. I replaced the reference to this class with my own SloppyUPNTranslator so that users can leave out the domain part of their UPN. Deploying is done by adding the class to a jar file and dropping it inside the lib/ directory within WEB-INF. My translator simply wraps a StandardTranslator and overrides the translate(String) method by adding a "@novay.nl" to the argument before calling the wrapped translator.

Am I finished playing with OAS as OpenID provider? For now. But there are plenty of loose ends that I intend to investigate later on:
  • Session identifiers when the user merely uses https://openid.novay.nl with an RP, the discovered URL can be used to track the user. I'd like to see if OAS can be tweaked to use a per-RP, or even per-session, pseudonym here.
  • Other, innovative, authentication methods. Perhaps smart card based.
  • Deploy OAS in Google App Engine. Not connected to our AD, of course. Just to see if it's possible: Looks like the server uses some threads, not sure if these are necessary.
Acknowledgment: Joost Reede of Alfa & Ariss was of invaluable help configuring the server and explaining OpenASelect basics. Thanks!