Authenticating in web applications

Allikas: eid.eesti.ee

Authentication in web applications

When to use eID for authentication in web apps

In Estonia there are three main ways for logging into a web application:

  • User name / password.
  • Facebook/Google/Twitter or other social media account.
  • eID either as an ID card or a mobile ID

The main advantages of eID over the other two are (a) significantly higher security (b) economizing on amount of work otherwise going into managing usernames/passwords: no need to create / replace lost passwords.

The main disadvantage is inconvenience for a user, who has to use an ID card or a mobile ID.

In the following we will first consider authentication using an ID card and then continue to authentication with mobile ID.


Implementing authentication with an ID card

The process contains three main steps:

  • Create the necessary certificate files: order from Sertifitseerimiskeskus (Certification Centre) a certificate for your own certificate file.
  • Configure the web server to use secure https along with the certificate files mentioned before. No programming needed here: the necessary functionality to communicate with an ID card over the internet has been built into the popular web servers Apache, IIS, Nginx.
  • Enhance your web app by reading and parsing ID card information from the environment variable set up by the web server. You have to program this functionality yourself: use the examples below to help reding/parsing.

Create the server certificate file

Use the openssl application and additional utilities wget, cat. etc.

In Debian you can install the necessary software by performing apt-get install openssl wget

All the following commands can be found from eID code examples as a file serveriserdid.sh.

Commands:

Create the file server.key:

 openssl genrsa -out server.key 1024
 

Create the file server.csr, answering several questions:

 openssl req -new -key server.key -out server.csr

For experimentation create a self-signed file server.crt:

 openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

In ordet to get a publicly acceptable server.crt file, ask it from an official issuer, for example http://sk.ee/teenused/veebiserveri-sertifikaadid

Get central certificates, check https://sk.ee/repositoorium/sk-sertifikaadid/

 wget http://sk.ee/upload/files/JUUR-SK.PEM.cer
 wget http://sk.ee/upload/files/EECCRCA.pem.cer
 wget http://sk.ee/upload/files/ESTEID-SK%202007.PEM.cer
 wget http://sk.ee/upload/files/ESTEID-SK%202011.pem.cer

Concatenate certificates to a single file:

 cat JUUR-SK.PEM.cer EECCRCA.pem.cer 'ESTEID-SK 2007.PEM.cer' 'ESTEID-SK 2011.pem.cer' > id.crt

Validating certificates

It is recommended to check the invalidation lists to obtain the revocation status of certificates. In case it is OK for your application to accept revoked certificates, you may skip this step.

There are two ways to check the certificates:

  • Use the Certificate Revocation List (CRL): a periodically downloadable list of all revoked certificates. Free to use from the http://sk.ee/
  • Use an Online Certificate Status Protocol (OCSP) which requires an agreement, see [1]


Creating revocation lists

Revocation lists are simpler and cheaper than OCSP. In the initial testing phase you can skip both.

Find the commands from eID code repository as a file tyhistusnimekirjad.sh.


Get revocation lists, see http://sk.ee/repositoorium/CRL/ :

 wget http://www.sk.ee/crls/esteid/esteid2007.crl
 wget http://www.sk.ee/crls/juur/crl.crl
 wget http://www.sk.ee/crls/eeccrca/eeccrca.crl
 wget http://www.sk.ee/repository/crls/esteid2011.crl

Convert revocation lists to PEM format:

 openssl crl -in esteid2007.crl -out esteid2007.crl -inform DER
 openssl crl -in crl.crl -out crl.crl -inform DER
 openssl crl -in eeccrca.crl -out eeccrca.crl -inform DER
 openssl crl -in esteid2011.crl -out esteid2011.crl -inform DER  

Create symlinks:

 ln -s crl.crl `openssl crl -hash -noout -in crl.crl`.r0
 ln -s esteid2007.crl `openssl crl -hash -noout -in esteid2007.crl`.r0
 ln -s eeccrca.crl `openssl crl -hash -noout -in eeccrca.crl`.r0
 ln -s esteid2011.crl `openssl crl -hash -noout -in esteid2011.crl`.r0  

See also:

or, as alternative:

Configuring the web server

We will cover configuring Apache. For IIS see Certification Center page.

You need to do three things to configure Apache:

  • Configure Apache to use the default secure https protocol.
  • Set the certification files created before (and optionally the revocation lists folder) in the https settings.
  • Restart Apache: first check that https functions OK.

Alternatively, see


https

In case https is not enabled yet, you need to configure it. In Debian you can do it like this:

 /etc/apache2/mods-enabled# ln -s ../mods-available/ssl.load ssl.load
 /etc/apache2/mods-enabled# ln -s ../mods-available/ssl.load ssl.conf
 /etc/apache2/sites-enabled# ln -s ../sites-available/default-ssl default-ssl

The normal ways to configure Apache may be fairly different for other distributions.

Restart Apache. In Debian:

 /etc/init.d/apache2 reload

Certificate files

The configuration file you need to change is:

  • For Debian typically /etc/apache2/sites-available/default-ssl
  • For Red Hat typically /etc/httpd/conf.d/ssl.conf
  • For Suse create your own file, for example /etc/apache2/vhosts.d/www.domain.ee.conf


The variables you should set are:

  • SSLEngine on
  • SSLCertificateFile /etc/apache2/eid/server.crt
  • SSLCertificateKeyFile /etc/apache2/eid/server.key
  • SSLCACertificateFile /etc/apache2/eid/id.crt
  • SSLCARevocationPath /etc/apache2/eid/
  • SSLVerifyClient require
  • SSLVerifyDepth 3

We assume here that the certificate files were installed into the folder /etc/apache2/eid/

NB! It is important how you set SSLCARevocationPath /etc/apache2/eid/ : if set, it must be a folder with the revocation list files. If these expire, Apache will decline to authenticate! You can also avoid setting SSLCARevocationPath, leaving it commented out: in this case revoked certificates will be, unfortunately, accepted.

Full example:

<IfModule mod_ssl.c>
<VirtualHost _default_:443>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www
        <Directory />
                Options FollowSymLinks
                AllowOverride None
        </Directory>
        <Directory /var/www/>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>
        ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
        <Directory "/usr/lib/cgi-bin">
                AllowOverride None
                Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
                Order allow,deny
                Allow from all
        </Directory>
        ErrorLog ${APACHE_LOG_DIR}/error.log
        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn
        CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined
        #   SSL Engine Switch:
        #   Enable/Disable SSL for this virtual host.
        SSLEngine on
        #   A self-signed (snakeoil) certificate can be created by installing
        #   the ssl-cert package. See
        #   /usr/share/doc/apache2.2-common/README.Debian.gz for more info.
        #   If both key and certificate are stored in the same file, only the
        #   SSLCertificateFile directive is needed.
        SSLCertificateFile   /etc/apache2/eid/server.crt 
        SSLCertificateKeyFile /etc/apache2/eid/server.key 
        #   Certificate Authority (CA):
        #   Set the CA certificate verification path where to find CA
        #   certificates for client authentication or alternatively one
        #   huge file containing all of them (file must be PEM encoded)
        #   Note: Inside SSLCACertificatePath you need hash symlinks
        #         to point to the certificate files. Use the provided
        #         Makefile to update the hash symlinks after changes.
        #SSLCACertificatePath /etc/ssl/certs/
        SSLCACertificateFile /etc/apache2/eid/id.crt 

        #   Certificate Revocation Lists (CRL):
        #   Set the CA revocation path where to find CA CRLs for client
        #   authentication or alternatively one huge file containing all
        #   of them (file must be PEM encoded)
        #   Note: Inside SSLCARevocationPath you need hash symlinks
        #         to point to the certificate files. Use the provided
        #         Makefile to update the hash symlinks after changes.
        SSLCARevocationPath /etc/apache2/eid/
        #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl

        #   Client Authentication (Type):
        #   Client certificate verification type and depth.  Types are
        #   none, optional, require and optional_no_ca.  Depth is a
        #   number which specifies how deeply to verify the certificate
        #   issuer chain before deciding the certificate is not valid.
        SSLVerifyClient require
        SSLVerifyDepth 3 

        <FilesMatch "\.(cgi|shtml|phtml|php)$">
                SSLOptions +StdEnvVars
        </FilesMatch>
        <Directory /usr/lib/cgi-bin>
                SSLOptions +StdEnvVars
        </Directory>
        #   "force-response-1.0" for this.
        BrowserMatch "MSIE [2-6]" \
                nokeepalive ssl-unclean-shutdown \
                downgrade-1.0 force-response-1.0
        # MSIE 7 and newer should be able to use keepalive
        BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost>
</IfModule>

Again restart Apache. In Debian:

 /etc/init.d/apache2 reload

IIS

see the IIS configuration guide

Add user authentication to your application

After the configuration steps covered, the web server will automatically guide the browser to ask for PIN, check the certificate and read data from the ID card into an environment variable on the server. The important environment variables are:

  • SSL_CLIENT_S_DN containing, for example: /C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37504170511/SN=SMITH/GN=JOHN/serialNumber=37504170511
  • SSL_CLIENT_VERIFY should have a value SUCCESS

Apache uses OPENSSL to get the SSL_CLIENT_S_DN value, encode the strings and organize data to the format /key=value/key=value/....

Different OPENSSL have been using different names for the fields.

Apache and OPENSSL also encode the non-ASCII characters into a 7-bit ASCII string. In normal cases the non-ascii characters are encoded to hex format like \x01

The ways to encode non-ascii characters differ in old and new ID cards:

  • old certificates use UCS-2/UTF-16: all characters take two bytes.
  • new certificates use UTF-8.

For example, "MÄNNIK" in a new certificate will be finally encoded as M\xC3\x84NNIK

We will bring three tiny example applications in PHP, Python and Ruby. The application reads the environment variable, converts character sets and parses the name and ID code of the user: these will be shown on the web page as a result of authentication.

We assume modern OPENSSL. Both the old and the new certificates are supported.

All the code examples can be found from the eID code repository.

PHP example

<?php 
header ("Content-Type: text/html; charset=utf-8");
  
// Get user information obtained by Apache from the Estonian ID card.
// Return list [last_name,first_name,person_code] or False if fails. 

function get_user () {
  // get relevant environment vars set by Apache
  // SSL_CLIENT_S_DN example:
  //  /C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37504170511/
  //  SN=SMITH/GN=JOHN/serialNumber=37504170511
  $ident=getenv("SSL_CLIENT_S_DN");  
  $verify=getenv("SSL_CLIENT_VERIFY");
  // check and parse the values
  if (!$ident || $verify!="SUCCESS") return False;
  $ident=certstr2utf8($ident); // old cards use UCS-2, new cards use UTF-8
  if (strpos($ident,"/C=EE/O=ESTEID")!=0) return False;
  $ps=strpos($ident,"/SN=");
  $pg=strpos($ident,"/GN=");
  $pc=strpos($ident,"/serialNumber=");
  if (!$ps || !$pg || !$pc) return False;
  $res=array(substr($ident,$ps+4,$pg-($ps+4)), 
             substr($ident,$pg+4,$pc-($pg+4)), 
             substr($ident,$pc+14,strlen($ident)-$pc+14));
  return $res;
}  

// Convert names from UCS-2/UTF-16 to UTF-8.
// Function taken from help.zone.eu.

function certstr2utf8 ($str) {
  $str = preg_replace ("/\\\\x([0-9ABCDEF]{1,2})/e", "chr(hexdec('\\1'))", $str);       
  $result="";
  $encoding=mb_detect_encoding($str,"ASCII, UCS2, UTF8");
  if ($encoding=="ASCII") {
    $result=mb_convert_encoding($str, "UTF-8", "ASCII");
  } else {
    if (substr_count($str,chr(0))>0) {
      $result=mb_convert_encoding($str, "UTF-8", "UCS2");
    } else {
      $result=$str;
    }
  }
  return $result;
}

//  Actual script to run

$user=get_user();
if (!$user) echo ("Authentication failed.");
else {
  echo "Last name: " . $user[0] . "<br>";
  echo "First name: " . $user[1] . "<br>";
  echo "Person code: " . $user[2] . "<br>";
}

?>

Python example

#!/usr/bin/python
# -*- coding: utf-8 -*-

import os, re

def get_user():
  """Get user information obtained by Apache from the Estonian ID card.
     Returns list [last_name,first_name,person_code] or None if fails. 
  """
  # get relevant environment vars set by Apache
  # SSL_CLIENT_S_DN example:
  # /C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37504170511/
  # SN=SMITH/GN=JOHN/serialNumber=37504170511
  ident=os.getenv('SSL_CLIENT_S_DN')
  verify=os.getenv('SSL_CLIENT_VERIFY')
  # check and parse the values
  if not ident or verify!="SUCCESS": return None
  ident=str2utf8(ident) # old cards use UCS-2, new cards use UTF-8
  if not ident.startswith("/C=EE/O=ESTEID"): return None
  ps=ident.find("/SN=")
  pg=ident.find("/GN=")
  pc=ident.find("/serialNumber=")
  if ps<=0 or ps<=0 or pc<=0: return None
  res=[ident[ps+4:pg], ident[pg+4:pc], ident[pc+14:]]
  return res

def str2utf8(s):
  """Convert names from UCS-2/UTF-16 to UTF-8.""" 
  try:
    s=re.sub("/\\\\x([0-9ABCDEF]{1,2})/e", "chr(hexdec('\\1'))", s);
    if s.find("\x00")>0: 
      x=s.decode(encoding="utf-16") 
      s=x.encode(encoding="utf-8")    
      return s
    return s
  except:
    return "" # conversion failed    

# Actual script to run  

print("Content-type: text/html; charset=utf-8\n\n")
user=get_user()
if not user: 
  print("Authentication failed.")
else:
  print("Last name: "+user[0]+"<br>")
  print("First name: "+user[1]+"<br>")
  print("Person code: "+user[2]+"<br>")

Ruby example

#!/usr/bin/ruby

require 'iconv'

# Get user information obtained by Apache from the Estonian ID card.
# Return list [last_name,first_name,person_code] or nil if fails. 

def get_user
  # Get user information obtained by Apache from the Estonian ID card.
  #  Returns list [last_name,first_name,person_code] or nil if fails. 
  # get relevant environment vars set by Apache
  # SSL_CLIENT_S_DN example:
  # /C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37504170511/
  # SN=SMITH/GN=JOHN/serialNumber=37504170511
  ident=ENV["SSL_CLIENT_S_DN"]
  verify=ENV["SSL_CLIENT_VERIFY"];
  # check and parse the values
  if !ident or verify!="SUCCESS"
    return nil
  end    
  ident=str2utf8(ident) # old cards use UCS-2, new cards use UTF-8
  if ident.index("/C=EE/O=ESTEID")!=0
    return nil
  end  
  ps=ident.index("/SN=")
  pg=ident.index("/GN=")
  pc=ident.index("/serialNumber=")
  if !ps or !pg or !pc
    return nil
  end  
  res=[ident[ps+4..pg-1], ident[pg+4..pc-1], ident[pc+14..-1]]
  return res
end  

# Convert names from UCS-2/UTF-16 to UTF-8.

def str2utf8(s)
  begin
    s=s.gsub("/\\\\x([0-9ABCDEF]{1,2})/e", "chr(hexdec('\\1'))")
    if s.index("\x00")
      conv=Iconv.new("UTF-8//IGNORE","UTF-16")
      s=conv.iconv(s) 
      return s
    end  
    return s
  rescue
    return "" # conversion failed 
  end  
end    

# Actual script to run

puts "Content-type: text/html; charset=utf-8\n\n"
user=get_user()
if !user 
  puts "Authentication failed."
else
  puts "Last name: "+user[0]+"<br>"
  puts "First name: "+user[1]+"<br>"
  puts "Person code: "+user[2]+"<br>"
end  

Personal identification with mobile ID

See [2][3][4]

  1. An ordinary SSL/TLS session is created without requesting the client's certificate.
  2. User inputs his or her phone number and/or personal identification number and the code of its issuer state. These data are sent to the DigiDocService service using the MobileAuthenticate query.
  3. DigiDocService verifies via the OCSP protocol that the personal identification certificate of the user's mobile ID is valid.
  4. DigiDocService performs the following simultaneously:
    • Sends a personal identification query to the mobile operator (who will forward it to the user's phone);
    • Returns a check code and the user's certificate.
      • Attention: The service returns only the certificate of the person who is being identified -- this does not mean that the user has been authenticated!
  5. The application displays a four-digit personal identification query check code to the user.
  6. The application will now periodically go to DigiDocService to query for the status of the personal identification using the GetMobileAuthenticateStatus query.
    • The status query can also be done synchronously, i.e. DigiDocService keeps the connection open and does not return anything until an answer has come from the mobile phone.
  7. A message about the personal identification query will be displayed on the user's phone
  8. The user makes sure the check code displayed on the phone is the same as the one displayed by the application and inputs mobile ID PIN1.
  9. The challenge issued by the service is signed with the secret key on the SIM card and the result sent to the mobile operator, who will forward it to DigiDocService.
  10. The next time the application queries the service for query status, the service responds that the user has been authenticated and returns the signature.

Usage examples can be seen in the Claims use case (Mobile ID section) and a SK example application can be found here.


See example and source from the Certification Center