Cloud Security: SAML, Shibboleth and Securing REST API for public cloud

In this blog post:

  • Why install Shibboleth on Amazon EC2 instance (or private cloud)
  • Securing application deployed to PaaS with Shibboleth SP
  • Resolving security concern of the cloud deployment with HTTPS and Private key encryption.
  • Code samples in Java and Groovy for securing REST API

What is SAML:

The best definition for SAML I found to be this one: SAML stands for “Security Assertion Markup Language,” and is an XML-based standard for communicating identity information between organizations. The primary function of SAML is to provide Internet Single Sign-On (SSO) for organizations looking to securely connect to Internet applications that exist both inside and outside the safety of an organization’s firewall.
In practice it means that one organization (Identity Provider IDP) and other (Service Provider SP) have a secure way to exchange information. For example, relations between university (Identity Provider) and outside vendor of e-learning system (Service Provider). SP secure consume identity information from IDP.
What is Shibboleth SP:

There are different implementations of SAML for different languages and platforms, but I found that Shibboleth SP, version 2.4 is a mature and easy to use implementation for the Service Provider. Shibboleth is very popular in Higher Education community and for a reason. The product provides simple integration with different federations around the globe – InCommon in US, SWISS in Europe, and so on. It allows engineers to easily configure to exchange data with other partners, who are not part of any federations via configurable xml files. Support for this open source product is excellent: The mailing list is very active, lead developers are always answering questions and helping out, as well as experienced members of the community. So I found it a very well done and well supported product. That’s a good part. Now is the challenge: The Shibboleth SP is presently only implemented in C++ as a module for Apache, IIS, and NSAPI. Moreover, supported platforms with binary distribution are limited to:

Shibboleth Supported platforms:

  • Red Hat Enterprise and CentOS 4, 5, 6 (i386 and x86_64)
  • SUSE Linux Enterprise Server 9, 10, 11, 11-SP1 (i386 and x86_64)
  • OpenSUSE Linux 11.0, 11.1, 11.2, 11.3, 11.4, 12.1 (i386 and x86_64)

This is quite a good list, but on all other platform you can try to build from the source, but if you are not experienced in sysadmin it can be a challenge. From a developer’a point of view, I want to install Shibboleth SP as simple as possible, configure it and concentrate on the application.

Why Amazon AWS or other IaaS cloud providers:
We want to install Shibboleth SP on a supported platform, for example CentOS 6 but IT does not support one or have any available. In this situation cloud services, specifically IaaS (Infrastructure as Service) platform provide huge benefits. In practical terms, developers don’t have to put a large proposal together that includes servers, storage, network, rack space, installation, configuration, and so on. They don’t have to wait hours for sysadmins to provision a host. They don’t have to wait days for an order to get delivered and installed. Instead, with IaaS, they just need a little cash and a few minutes to pick what host they want. So we want CentOs 6 on Amazon EC2 and install Shibboleth SP on it. I am not going to repeat excellent guides on the topic of shibboleth installation, you can check this one:

Another benefit of EC2 deployment is that if a developer needs another instance for testing and staging, he can recreate one from an already working environment by copying from an original instance. I would prefer using proper tools for this particular activity – puppet or chef, for example by writing a chief recipe to install and configure shibboleth, apache, tomcat and so on.

Application deployed to PaaS (Heroku, cloudbees, e.t. c.)
You have one or several applications you want to deploy into PaaS platform, for example to Heroku or CloudBees. Here are the reasons why you want to do it: PaaS provides a care free environment for developers to work. It lets them focus on code and not have to worry about configuration and maintenance of the underlying platform. By utilizing PaaS, developers simply pick the languages and features they want, and match those requirements with a provider that has them, and start coding.
And you have to put those applications under Shibboleth Single-Sign-On.
The problem for our user’s case is that PaaS usually provides only a run time environment, for example Tomcat, JBoss or Glassfish instances for JVM or Python runtime and libraries or Ruby environments. There is no way you can install Shibboleth SP in front of it as an Apache module. There is simply no Apache, and even if there is one; PaaS providers don’t provide access to it.
One of the solutions is to create a small custom application on the same EC2 instance you have Shibboleth SP2 installed. This application will work as REST API endpoint to get assertions from Shibboleth, encode them and provide REST API to serve those assertions to other applications. Java developers can install Tomcat behind the Apache server and configure it to talk to Shibboleth SP with mod_ajp or mod_jk.
Because both of them are on a public cloud, data security is a concern. The more sensitive the data is, the more reason there is for concern. As software developers, it’s important that we understand both the real security risks of cloud computing and the realistic approaches to solving at least some of these concerns.

I choose to use private key security the Java platform’s built-in private-key encryption standards and utilities to reasonably secure your data, even if it is stored on a distributed cloud datastore. In private-key encryption, data is encrypted and decrypted using a single private key. This type of encryption makes it hard to share encrypted data with a third party because both the sender and receiver must use the same key. If that key is compromised, then all of the encrypted information is compromised. Private-key encryption is highly effective if the data being encrypted doesn’t need to be shared with other parties, so that the key can at all times be kept under tight control.

Private-key encryption is an effective means of securing application data that will be stored and transmitted via a cloud infrastructure. Because the encryption key remains in the control of an administrator or application creator, cloud providers and other potential eavesdroppers do not have uncontrolled access to that data. We will use HTTPS protocol for all communication between PaaS and our Service Provider. This is very important; otherwise we have to encode all transmission of data completely.

Communication workflow:

We have to ensure that communication initiated by trusted client, in our case our PaaS application. Because key is the most important part, we can opt of generating key in the code,
so nobody, even person who has access to the service, can get to our key.

  1. Application on PaaS initialize lazy session to IDP (
  2. SP redirect user browser to IPD login Page (SAML2)
  3. IDP redirect user back to SP with encoded assertions/ user attributes
  4. SP may store assertion temporarily in key value store (Memcached, Redis) in encrypted form
  5. SP redirect user browser to secure resource on PaaS application
  6. Depending on PaaS application security implementation, for example, Spring security filters, application issue callback to SP to get attributes/assertions.This should always go over HTTPS and include private key encryption of data so server (SP) know the RESR API call originated from a legitimate client. Simular to AWS Web services API
  7. Clent (PaaS application) received the assertion and authenticate the user

Secure communication via REST API:
In plain English:

server and a client know a public and private key; only the server and client know the private key, but everyone can know the public key… who cares what they know.

A client creates a unique HMAC (hash) representing it’s request to the server. It does this by combining the request data (arguments and values or XML/JSON or whatever it was planning on sending) and hashing the blob of request data along with the private key.

The client then sends that HASH to the server, along with all the arguments and values it was going to send anyway.

The server gets the request and re-generates it’s own unique HMAC (hash) based on the submitted values using the same methods the client used.

The server then compares the two HMACs, if they are equal, then the server trusts the client, and runs the request.

Interaction between client (PaaS deployed Application) and server (Shibboleth SP protected servlet) looks like this:
[CLIENT] Before making the REST API call, combine a bunch of unique data together (this is typically all the parameters and values you intend on sending)
[CLIENT] Hash (HMAC-SHA1 or SHA256, or ASA preferably) the blob of data data (from Step #1) with your private key assigned to you by the system.
[CLIENT] Send the server the following data:
Some user-identifiable information like an “API Key”, client ID, user ID or something else it can use to identify who you are. This is the public API key, never the private API key. This is a public value that anyone (even evil masterminds can know and you don’t mind). It is just a way for the system to know WHO is sending the request, not if it should trust the sender or not (it will figure that out based on the HMAC).
Send the HMAC (hash) you generated.
Send all the data (parameters and values) you were planning on sending anyway. Probably unencrypted if they are harmless values, like “mode=start&number=4&order=desc” or other operating nonsense. If the values are private, you’ll need to encrypt them.
(OPTIONAL) The only way to protect against “replay attacks” on your API is to include a timestamp of time kind along with the request so the server can decide if this is an “old” request, and deny it. The timestamp must be included into the HMAC generation (effectively stamping a created-on time on the hash) in addition to being checked “within acceptable bounds” on the server.
[SERVER] Receive all the data from the client.
[SERVER] (see OPTIONAL) Compare the current server’s timestamp to the timestamp the client sent. Make sure the difference between the two timestamps it within an acceptable time limit (5-15mins maybe) to hinder replay attacks.
NOTE: Be sure to compare the same timezones and watch out for issues that popup with daylight savings time change-overs.
UPDATE: As correctly pointed out by a few folks, just use UTC time and forget about the DST issues.
[SERVER] Using the user-identifying data sent along with the request (e.g. API Key) look the user up in the DB and load their private key.
[SERVER] Re-combine the same data together that the client did in the same way the client did it. Then hash (generate HMAC) that data blob using the private key you looked up from the DB.
(see OPTIONAL) If you are protecting against replay attacks, include the timestamp from the client in the HMAC re-calculation on the server. Since you already determined this timestamp was within acceptable bounds to be accepted, you have to re-apply it to the hash calculation to make sure it was the same timestamp sent from the client originally, and not a made-up timestamp from a man-in-the-middle attack.
[SERVER] Run that mess of data through the HMAC hash, exactly like you did on the client.
[SERVER] Compare the hash you just got on the server, with the hash the client sent you; if they match, then the client is considered legit, so process the command. Otherwise reject the command! Access Denied!

Implementation:

public interface Cryptographical {
 String encrypt(String plaintext);
 String decrypt(String ciphertext);
}

Key Interface – wrapper to Java key:
public interface CryptoKeyable {
    Key getKey() throws NoSuchAlgorithmException;

}

An AES implementation of my Cryptographical interface:

public class AESCryptoImpl implements Cryptographical {

 private Key key;
 private Cipher ecipher;
 private Cipher dcipher;

 private AESCryptoImpl(Key key) throws NoSuchAlgorithmException,
   NoSuchPaddingException, InvalidKeyException {
  this.key = key;
  this.ecipher = Cipher.getInstance("AES");
  this.dcipher = Cipher.getInstance("AES");
  this.ecipher.init(Cipher.ENCRYPT_MODE, key);
  this.dcipher.init(Cipher.DECRYPT_MODE, key);
 }

 public static Cryptographical initialize(CryptoKeyable key) throws CryptoException {
  try {
   return new AESCryptoImpl(key.getKey());
  } catch (NoSuchAlgorithmException e) {
   throw new CryptoException(e);
  } catch (NoSuchPaddingException e) {
   throw new CryptoException(e);
  } catch (InvalidKeyException e) {
   throw new CryptoException(e);
  }
 }

 public String encrypt(String plaintext) {
  try {

   return new BASE64Encoder().encode(ecipher.doFinal(plaintext.getBytes("UTF8")));
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }

 public String decrypt(String ciphertext) {
  try {
   return new String(dcipher.doFinal(new BASE64Decoder().decodeBuffer(ciphertext)),
     "UTF8");
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
}

Exceptions:

public class CryptoException extends Exception {
    public CryptoException(String s) {
        super("Crypto exception: "+s);
    }

    public CryptoException(Throwable throwable) {
        super(throwable);
    }

Crypto Key:

public class AESCryptoKey implements CryptoKeyable{
    private Key key;

    public AESCryptoKey(Key key) {
        this.key=key;
    }

    public Key getKey() throws NoSuchAlgorithmException
    {
       return this.key;
    }
}

Groovy script to create a key file:

// groovy

ClientAPIKey='DigitalLibraryPaas'
KeyGenerator generator = KeyGenerator.getInstance("AES")
generator.init(256)
SecretKey key=generator.generateKey()
KeyStore ks = KeyStore.getInstance("JCEKS")
         ks.load(null, null)
         KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(key)
         ks.setEntry('shibboleth_sp', skEntry, new KeyStore.PasswordProtection("mysuperduperlongpassword!123?".toCharArray()))
         def f=new File("${ClientAPIKey}.keystore")
         ks.store(f.newDataOutputStream(), "keypassword45#22!AHGT".toCharArray());

Groovy/spock unit test for our user cases:

class EncryptedCommunicationSpec extends Specification{
    def "Client - Server secure communication with encryption and key file" () {
        given: "Client has API Key, and data to send over to Rest API"
            def data="/session/12343"
        def ClientAPIKey='DigitalLibraryPaas'
            
        when: "Client encrypt and send data over to server"
        File f=new File("${ClientAPIKey}.keystore")
        KeyStore ks = KeyStore.getInstance("JCEKS");
        ks.load(f.newDataInputStream(),'keypassword45#22!AHGT'.toCharArray())

        KeyStore.SecretKeyEntry skEntry=ks.getEntry("shibboleth_sp",
                new KeyStore.
               PasswordProtection("mysuperduperlongpassword!123?".toCharArray()))
        Cryptographical crypto = AESCryptoImpl.initialize(
new AESCryptoKey(skEntry.secretKey))
        def encData = crypto.encrypt(data);
        
        println(encData)
        
        and:  "server receive and decrypt data"
            File fs=new File("${ClientAPIKey}.keystore")
                KeyStore key_server = KeyStore.getInstance("JCEKS");
                key_server.load(fs.newDataInputStream(),
'keypassword45#22!AHGT'.toCharArray())
                KeyStore.SecretKeyEntry entry=ks.getEntry("shibboleth_sp",
                        new KeyStore.PasswordProtection("mysuperduperlongpassword!123?"
                       .toCharArray()))
                Cryptographical crypto_server = AESCryptoImpl.initialize(new AESCryptoKey(entry.secretKey))
                def encDataServer=crypto_server.encrypt(data)

                print encDataServer
        then: "server and client encryption with key produce the same hash"
             crypto.decrypt(encData)==data
             crypto.decrypt(encDataServer)==data
             encData == encDataServer


    }

    def "Client- Server secure communication with wrong key file" () {
            given: "Client has API Key, and data to send over to Rest API"
                def data="/session/12343"
                def ClientAPIKey='DigitalLibraryPaas'

            when: "Client encrypt and send data over to server"
        File f=new File("${ClientAPIKey}.keystore")
            KeyStore ks = KeyStore.getInstance("JCEKS");
            ks.load(f.newDataInputStream(),'keypassword45#22!AHGT'.toCharArray())
            KeyStore.SecretKeyEntry skEntry=ks.getEntry("shibboleth_sp",
                    new KeyStore.PasswordProtection
                    ("mysuperduperlongpassword!123?".toCharArray()))
            Cryptographical crypto = AESCryptoImpl.initialize(new AESCryptoKey(skEntry.secretKey))
            def encData = crypto.encrypt(data);
            println(encData)

            and:  "server receive and decrypt data"
                    File fs1=new File('shibboleth_sp_bad.keystore')   // different keystore
                    KeyStore key_server2 = KeyStore.getInstance("JCEKS");
                    key_server2.load(fs1.newDataInputStream(),
'keypassword45#22!AHGT'.toCharArray())
                    KeyStore.SecretKeyEntry entry2=key_server2.getEntry("shibboleth_sp",
                            new KeyStore.PasswordProtection("mysuperduperlongpassword!123?".toCharArray()))

                    Cryptographical crypto_server = AESCryptoImpl.initialize(new AESCryptoKey(entry2.secretKey))
                    def encDataServer=crypto_server.encrypt(data)
                    println('root '+ encDataServer)
            then: "server and client encryption with key produce the same hash"
                 encData != encDataServer


        }



}

If you use password with more than 7 character, you may need to download and install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files. Otherwise you may get cryptography exception:
Illegal key size or default parameters.

This configuration provide secure access to resources protected by SAML2 compliant Shibboleth Service Provider and allow to deploy applications to the cloud securely.

Resources:

1 Comment

  1. Here’s what I don’t get. Why even bother with a timestamp. Simply use a temporary authentification code that is generated at the initial request, and trashed once it’s no longer needed.

    [CLIENT] Sends Public Code
    [SERVER] Return temporary authentification code
    [CLIENT] Sends Public Code, Temp Code and hashed data
    [SERVER] Processes request, returns information and destroys temporary code (so it can’t be used by anyone else). If temp code is not used within a limited time (15 minutes for example) it is trashed.

    The only time data is returned is after the SP receives the temp code, limiting the timeframe with which it can be used.

    Also, what exactly is it that Shibboleth does that can’t easily be hand coded (creating the hashed data isn’t that hard)

    Reply

Submit a Comment

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>