add HandleClient implementation + configuration + tests
authorymh <ymh.work@gmail.com>
Fri, 22 Apr 2016 11:20:17 +0200
changeset 153 338bcc78d431
parent 152 dd6b3adde73b
child 154 ded3cf22eef8
add HandleClient implementation + configuration + tests
.hgignore
dev/provisioning/custom.yaml.tmpl
dev/provisioning/modules/sysconfig/files/handle/handle_script_corpus_admin_dsa_keys.exp
dev/provisioning/modules/sysconfig/manifests/handle.pp
dev/provisioning/modules/sysconfig/manifests/params.pp
dev/provisioning/modules/sysconfig/templates/handle/handle_config.dct.erb
dev/provisioning/modules/sysconfig/templates/handle/handle_data.sql.sh.erb
server/src/.env.example
server/src/app/Console/Commands/ManageHandle.php
server/src/app/Libraries/Handle/HandleClient.php
server/src/composer.json
server/src/composer.lock
server/src/config/corpusparole.php
server/src/tests/Libraries/Handle/HandleClientIntegrationTest.php
server/src/tests/Libraries/Handle/HandleClientTest.php
--- a/.hgignore	Wed Apr 13 13:38:40 2016 +0200
+++ b/.hgignore	Fri Apr 22 11:20:17 2016 +0200
@@ -1,4 +1,5 @@
 syntax: regexp
+^\.projectile$
 ^dev2
 ^dev/\.vagrant
 ^dev/handle
@@ -8,6 +9,8 @@
 ^server/data
 ^server/src/\.git.*
 ^server/src/\.env
+^server/src/.dir-locals.el$
+^server/src/.projectile$
 ^server/src/composer.phar$
 ^server/src/vendor
 ^server/src/node_modules
--- a/dev/provisioning/custom.yaml.tmpl	Wed Apr 13 13:38:40 2016 +0200
+++ b/dev/provisioning/custom.yaml.tmpl	Fri Apr 22 11:20:17 2016 +0200
@@ -28,9 +28,13 @@
 
 # handle host
 sysconfig::params::hdl_host    : 172.16.1.6
+# handle db name
+sysconfig::params::hdl_db_name : handle
+# handle prefix
+sysconfig::params::hdl_prefix  : "12345.100"
+# handle test prefix
+sysconfig::params::hdl_test_prefix  : "12345.101"
 
-# handle database
-sysconfig::params::hdl_db_name : handle
 
 # memory percentage used by memcached
 sysconfig::params::memcached_max_memory : 25%
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dev/provisioning/modules/sysconfig/files/handle/handle_script_corpus_admin_dsa_keys.exp	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,53 @@
+#!/bin/expect -f
+#
+# This Expect script was generated by autoexpect on Wed Mar 23 13:16:38 2016
+# Expect and autoexpect were both written by Don Libes, NIST.
+#
+# Note that autoexpect does not guarantee a working script.  It
+# necessarily has to guess about certain things.  Two reasons a script
+# might fail are:
+#
+# 1) timing - A surprising number of programs (rn, ksh, zsh, telnet,
+# etc.) and devices discard or ignore keystrokes that arrive "too
+# quickly" after prompts.  If you find your new script hanging up at
+# one spot, try adding a short sleep just before the previous send.
+# Setting "force_conservative" to 1 (see below) makes Expect do this
+# automatically - pausing briefly before sending each character.  This
+# pacifies every program I know of.  The -c flag makes the script do
+# this in the first place.  The -C flag allows you to define a
+# character to toggle this mode off and on.
+# exp_internal 1
+set force_conservative 0  ;# set to 1 to force conservative mode even if
+                          ;# script wasn't run conservatively originally
+if {$force_conservative} {
+        set send_slow {1 .1}
+        proc send {ignore arg} {
+                sleep .1
+                exp_send -s -- $arg
+        }
+}
+
+#
+# 2) differing output - Some programs produce different output each time
+# they run.  The "date" command is an obvious example.  Another is
+# ftp, if it produces throughput statistics at the end of a file
+# transfer.  If this causes a problem, delete these patterns or replace
+# them with wildcards.  An alternative is to use the -p flag (for
+# "prompt") which makes Expect only look for the last line of output
+# (i.e., the prompt).  The -P flag allows you to define a character to
+# toggle this mode off and on.
+#
+# Read the man page for more info.
+#
+# -Don
+
+
+set timeout -1
+spawn /opt/handle/bin/hdl-keygen -alg dsa -keysize 2048 /var/lib/handle/corpusadmdsapriv.bin /var/lib/handle/corpusadmdsapub.bin
+match_max 5000
+
+
+expect -re "Would you like to encrypt your private key\\\? \\\(y\\\/n\\\) \\\[y\\\] $"
+send -- "n\r"
+
+expect eof
--- a/dev/provisioning/modules/sysconfig/manifests/handle.pp	Wed Apr 13 13:38:40 2016 +0200
+++ b/dev/provisioning/modules/sysconfig/manifests/handle.pp	Fri Apr 22 11:20:17 2016 +0200
@@ -5,6 +5,7 @@
     $hdl_web_port       = hiera('sysconfig::params::hdl_web_port', $sysconfig::params::hdl_web_port),
     $hdl_key_pswd       = hiera('sysconfig::params::hdl_key_pswd', $sysconfig::params::hdl_key_pswd),
     $hdl_prefix         = hiera('sysconfig::params::hdl_prefix', $sysconfig::params::hdl_prefix),
+    $hdl_test_prefix    = hiera('sysconfig::params::hdl_test_prefix', $sysconfig::params::hdl_test_prefix),
     $hdl_prefix_admin   = hiera('sysconfig::params::hdl_prefix_admin', $sysconfig::params::hdl_prefix_admin),
     $hdl_db_name        = hiera('sysconfig::params::hdl_db_name', $sysconfig::params::hdl_db_name),
     $db_user            = hiera('sysconfig::params::db_user', $sysconfig::params::db_user),
@@ -109,6 +110,77 @@
         require => [Archive["handle-8.1"], Package['java-install'], Package['expect']]
     }
 
+    exec { "convert-handle-corpus-admin-keys-priv":
+        command => "/opt/handle/bin/hdl-convert-key /var/lib/handle/corpusadmpriv.bin -o /var/lib/handle/corpusadmpriv.pem",
+        cwd => "/opt/handle/bin",
+        creates => "/var/lib/handle/corpusadmpriv.pem",
+        user => $hdl_user,
+        require => [Archive["handle-8.1"], Package['java-install'], Exec['init-handle-corpus-admin-keys']]
+    }
+
+    exec { "convert-handle-corpus-admin-keys-pub":
+        command => "/opt/handle/bin/hdl-convert-key /var/lib/handle/corpusadmpub.bin -o /var/lib/handle/corpusadmpub.pem",
+        cwd => "/opt/handle/bin",
+        creates => "/var/lib/handle/corpusadmpub.pem",
+        user => $hdl_user,
+        require => [Archive["handle-8.1"], Package['java-install'], Exec['init-handle-corpus-admin-keys']]
+    }
+    
+    # create handle admin handle dsa keys
+    file { "/tmp/handle/handle_script_corpus_admin_dsa_keys.exp":
+        ensure => file,
+        source => "puppet:///modules/sysconfig/handle/handle_script_corpus_admin_dsa_keys.exp",
+        owner => "$hdl_user",
+        group => "$hdl_user",
+        mode => "0750"
+    }->
+    exec { "init-handle-corpus-admin-dsa-keys":
+        command => "/tmp/handle/handle_script_corpus_admin_dsa_keys.exp",
+        cwd => "/opt/handle/bin",
+        provider => 'shell',
+        creates => "/var/lib/handle/corpusadmdsapriv.bin",
+        user => $hdl_user,
+        notify => Exec['rm-tmp-handle-corpus-admin-dsa-key'],
+        require => [Archive["handle-8.1"], Package['java-install'], Package['expect']]
+    }
+
+
+    exec { "convert-handle-corpus-admin-dsa-keys-pub":
+        command => "/opt/handle/bin/hdl-convert-key /var/lib/handle/corpusadmdsapub.bin -o /var/lib/handle/corpusadmdsapub.pem",
+        cwd => "/opt/handle/bin",
+        creates => "/var/lib/handle/corpusadmdsapub.pem",
+        user => $hdl_user,
+        require => [Archive["handle-8.1"], Package['java-install'], Exec['init-handle-corpus-admin-dsa-keys']]
+    }
+
+    exec { "convert-handle-corpus-admin-dsa-keys-priv":
+        command => "/opt/handle/bin/hdl-convert-key /var/lib/handle/corpusadmdsapriv.bin -o /var/lib/handle/corpusadmdsapriv.pem",
+        cwd => "/opt/handle/bin",
+        creates => "/var/lib/handle/corpusadmdsapriv.pem",
+        user => $hdl_user,
+        require => [Archive["handle-8.1"], Package['java-install'], Exec['init-handle-corpus-admin-dsa-keys']]
+    }  
+
+    
+    exec { "convert-handle-corpus-admin-cert":
+        command => "/usr/bin/openssl req -pubkey -x509 -new -sha256 -subj \"/CN=300:${hdl_prefix}\\/${hdl_prefix_admin}\" -key /var/lib/handle/corpusadmpriv.pem | /usr/bin/openssl x509 -inform PEM -out /var/lib/handle/corpusadmcrt.pem",
+        creates => "/var/lib/handle/corpusadmcrt.pem",
+        user => $hdl_user,
+        require => Exec["convert-handle-corpus-admin-keys-priv"]
+    }->
+    exec { "concat-handle-corpus-admin-pkey-cert":
+    	 command => "/usr/bin/cat /var/lib/handle/corpusadmpriv.pem /var/lib/handle/corpusadmcrt.pem > /var/lib/handle/corpusadmpkeycrt.pem",
+	 creates => "/var/lib/handle/corpusadmpkeycrt.pem",
+	 user => $hdl_user
+    }
+
+    exec { "convert-handle-corpus-admin-test-cert":
+        command => "/usr/bin/openssl req -pubkey -x509 -new -sha256 -subj \"/CN=300:${hdl_test_prefix}\\/${hdl_prefix_admin}\" -key /var/lib/handle/corpusadmpriv.pem | /usr/bin/openssl x509 -inform PEM -out /var/lib/handle/corpusadmcrttest.pem",
+        creates => "/var/lib/handle/corpusadmcrttest.pem",
+        user => $hdl_user,
+        require => Exec["convert-handle-corpus-admin-keys-priv"]
+    }
+
     file { '/tmp/handle/handle_data.sql.sh':
         ensure => file,
         content => template("sysconfig/handle/handle_data.sql.sh.erb"),
@@ -208,6 +280,11 @@
         refreshonly => true
     }
 
+    exec { "rm-tmp-handle-corpus-admin-dsa-key":
+        command => "/usr/bin/rm -f /tmp/handle/handle_script_corpus_admin_dsa_keys.exp",
+        refreshonly => true
+    }
+    
     exec { "open-handle-web-port":
         command => "firewall-cmd --permanent --add-port=8000/tcp",
         path => [ '/bin', '/usr/bin', '/usr/local/bin' ]
--- a/dev/provisioning/modules/sysconfig/manifests/params.pp	Wed Apr 13 13:38:40 2016 +0200
+++ b/dev/provisioning/modules/sysconfig/manifests/params.pp	Fri Apr 22 11:20:17 2016 +0200
@@ -17,6 +17,7 @@
     $hdl_web_port       = hiera('sysconfig::params::hdl_web_port', '8000')
     $hdl_key_pswd       = hiera('sysconfig::params::hdl_key_pswd', 'corpus')
     $hdl_prefix         = hiera('sysconfig::params::hdl_prefix', '12345')
+    $hdl_test_prefix    = hiera('sysconfig::params::hdl_test_prefix', '12345.1')
     $hdl_prefix_admin   = hiera('sysconfig::params::hdl_prefix_admin', 'CORPUS_ADMIN')
     $hdl_db_name        = hiera('sysconfig::params::hdl_db_name', 'handle')
 
--- a/dev/provisioning/modules/sysconfig/templates/handle/handle_config.dct.erb	Wed Apr 13 13:38:40 2016 +0200
+++ b/dev/provisioning/modules/sysconfig/templates/handle/handle_config.dct.erb	Fri Apr 22 11:20:17 2016 +0200
@@ -36,7 +36,9 @@
   "server_config" = {
     "server_admins" = (
       "300:0.NA/<%= @hdl_prefix %>"
-    )
+      "300:0.NA/<%= @hdl_test_prefix %>"
+      "300:0.NA/<%= @hdl_test_prefix %>_DSA"
+   )
 
     "replication_admins" = (
       "300:0.NA/<%= @hdl_prefix %>"
@@ -60,6 +62,7 @@
 
     "auto_homed_prefixes" = (
       "0.NA/<%= @hdl_prefix %>"
+      "0.NA/<%= @hdl_test_prefix %>"
     )
 
   }
--- a/dev/provisioning/modules/sysconfig/templates/handle/handle_data.sql.sh.erb	Wed Apr 13 13:38:40 2016 +0200
+++ b/dev/provisioning/modules/sysconfig/templates/handle/handle_data.sql.sh.erb	Fri Apr 22 11:20:17 2016 +0200
@@ -1,6 +1,8 @@
 #!/usr/bin/env sh
 
 handle_admin_pubkey=$(hexdump -ve '1/1 "%.2x"' /var/lib/handle/corpusadmpub.bin);
+handle_admin_pubkey_dsa=$(hexdump -ve '1/1 "%.2x"' /var/lib/handle/corpusadmdsapub.bin);
+
 
 cat <<EOF > /tmp/handle/handle_data.sql
 -- MySQL dump 10.16  Distrib 10.1.13-MariaDB, for osx10.11 (x86_64)
@@ -25,8 +27,17 @@
 
 LOCK TABLES \`handles\` WRITE;
 /*!40000 ALTER TABLE \`handles\` DISABLE KEYS */;
+
 INSERT INTO \`handles\` VALUES ('<%= @hdl_prefix %>/<%= @hdl_prefix_admin %>',200,'HS_VLIST','300:0.NA/<%= @hdl_prefix %>; 300:<%= @hdl_prefix %>/<%= @hdl_prefix_admin %>;',0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
 INSERT INTO \`handles\` VALUES ('<%= @hdl_prefix %>/<%= @hdl_prefix_admin %>',300,'HS_PUBKEY',0x$handle_admin_pubkey,0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
+
+INSERT INTO \`handles\` VALUES ('<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>',200,'HS_VLIST','300:0.NA/<%= @hdl_test_prefix %>; 300:<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>; 300:<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>_DSA',0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
+INSERT INTO \`handles\` VALUES ('<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>',300,'HS_PUBKEY',0x$handle_admin_pubkey,0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
+
+
+INSERT INTO \`handles\` VALUES ('<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>',200,'HS_VLIST','300:0.NA/<%= @hdl_test_prefix %>; 300:<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>; 300:<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>_DSA',0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
+INSERT INTO \`handles\` VALUES ('<%= @hdl_test_prefix %>/<%= @hdl_prefix_admin %>_DSA',300,'HS_PUBKEY',0x$handle_admin_pubkey_dsa,0,86400,<%= scope.function_time([]) %>,'',1,1,1,0);
+
 /*!40000 ALTER TABLE \`handles\` ENABLE KEYS */;
 UNLOCK TABLES;
 
@@ -37,6 +48,7 @@
 LOCK TABLES \`nas\` WRITE;
 /*!40000 ALTER TABLE \`nas\` DISABLE KEYS */;
 INSERT INTO \`nas\` VALUES ('0.NA/<%= @hdl_prefix %>');
+INSERT INTO \`nas\` VALUES ('0.NA/<%= @hdl_test_prefix %>');
 /*!40000 ALTER TABLE \`nas\` ENABLE KEYS */;
 UNLOCK TABLES;
 /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
--- a/server/src/.env.example	Wed Apr 13 13:38:40 2016 +0200
+++ b/server/src/.env.example	Fri Apr 22 11:20:17 2016 +0200
@@ -34,3 +34,28 @@
 ELASTICSEARCH_INDEX='corpus'
 ELASTICSEARCH_SHARDS=1
 ELASTICSEARCH_REPLICAS=1
+
+HANDLE_HOST=172.16.1.6
+HANDLE_PORT=8000
+HANDLE_PREFIX=12345.100
+
+HANDLE_CERT_OR_PKEY=""
+HANDLE_PASSWORD=NULL
+
+HANDLE_TEST_PREFIX=12345.101
+
+
+HANDLE_TEST_CERT=""
+HANDLE_TEST_CERT_PASSWORD=NULL
+HANDLE_TEST_CERT_ADMIN_HANDLE=""
+
+HANDLE_TEST_RSA_KEY=""
+HANDLE_TEST_RSA_PASSWORD=NULL
+HANDLE_TEST_RSA_ADMIN_HANDLE=""
+
+
+HANDLE_TEST_DSA_KEY=""
+HANDLE_TEST_DSA_PASSWORD=NULL
+HANDLE_TEST_DSA_ADMIN_HANDLE=""
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/app/Console/Commands/ManageHandle.php	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,116 @@
+<?php
+
+namespace CorpusParole\Console\Commands;
+
+use Illuminate\Console\Command;
+
+use CorpusParole\Libraries\Handle\handleClient;
+
+class ManageHandle extends Command
+{
+    /**
+     * The name and signature of the console command.
+     *
+     * @var string
+     */
+    protected $signature = 'corpus-parole:manageHandle
+                                {--r|replace: Replace value for existing handles}
+                                {--k|key=: Private key}
+                                {--p|password=: key password}
+                                {--P|ask-password: ask for private key password}
+                                {--step-size=100 : number of documents to retrieve from repository at a time before indexing}';
+
+    /**
+     * The console command description.
+     *
+     * @var string
+     */
+    protected $description = 'Synchronize the handle registry with the content of the rdf repository.';
+
+
+    /**
+     * Create a new command instance.
+     *
+     * @return void
+     */
+    public function __construct(DocumentRepository $documentRepository)
+    {
+        $this->documentRepository = $documentRepository;
+        parent::__construct();
+    }
+
+    private function registerHandle($doc) {
+
+    }
+
+    /**
+     * Execute the console command.
+     *
+     * @return mixed
+     */
+    public function handle()
+    {
+        $stepSize = $this->option('step-size');
+
+        $passwordKey = $this->option('password');
+        if($this->option('ask-password')) {
+            $password = $this->secret('Private key password?');
+        }
+
+        $privateKey = $this->option('key');
+
+        $privateKeyRes = openssl_pkey_get_private($privateKey, $password);
+
+
+        // create handle client
+        $this->handleClient = new HandleClient($privateKeyRes);
+
+        $this->info('Adding documents handles...');
+
+        $lastPage = $this->documentRepository->paginateAll($stepSize, 'page')->lastPage();
+        $total = $this->documentRepository->getCount();
+
+        $progressBar = $this->output->createProgressBar($total);
+        $progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% - %message%');
+
+        for ($page=1;$page<=$lastPage;$page++)
+        {
+            $docs = $this->documentRepository->paginateAll($stepSize, 'page', $page);
+            foreach ($docs as $doc){
+                $this->registerHandle($doc);
+                $progressBar->setMessage($doc->getId());
+                $progressBar->advance();
+            }
+        }
+        $progressBar->finish();
+        $this->info('Adding handles completed');
+
+        $this->info('Removing extra handles...');
+
+        $lastPage = -1;
+        $page = 0;
+        $lastPage = 0;
+
+        while($lastPage<0 || $page <= $lastPage) {
+            $handle_res = $this->handleClient->paginateAll($stepSize, 'page', $page++);
+            foreach($handle_res as $handle) {
+                if(is_null($this->documentRepository->get($handle))) {
+                    $this->handleClient->deleteHandle($handle);
+                }
+                $progressBar->setMessage($handle);
+                $progressBar->advance();
+            }
+        }
+
+        $this->info('Removing extra handles completed');
+
+
+        // query ids
+        // loop on ids
+        // PUT handle
+        // query handle
+        // if not in rdf ids delete
+
+        // delete handle session
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/app/Libraries/Handle/HandleClient.php	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,279 @@
+<?php
+namespace CorpusParole\Libraries\Handle;
+
+use Log;
+
+
+use Illuminate\Pagination\LengthAwarePaginator;
+use Illuminate\Pagination\Paginator;
+
+use phpseclib\Crypt\RSA;
+
+// current_date = datetime.now()
+// current_date_format = unicode(current_date.strftime('%Y-%m-%dT%H:%M:%SZ'))
+// handle_record = {u'values': [
+//     {u'index': 1, u'ttl': 86400, u'type': u'URL', u'timestamp': current_date_format, u'data': {u'value': u'http://www.ribaenterprises.com', u'format': u'string'}},
+//     {u'index': 2, u'ttl': 86400, u'type': u'EMAIL', u'timestamp': current_date_format, u'data': {u'value': u'info@ribaenterprises.com', u'format': u'string'}},
+//     {u'index': 100, u'ttl': 86400, u'type': u'HS_ADMIN', u'timestamp': current_date_format, u'data': {u'value': {u'index': 200, u'handle': unicode(auth_id), u'permissions': u'011111110011'}, u'format': u'admin'}}
+// ], u'handle': unicode(handle), u'responseCode': 1}
+
+
+
+// class to handle communication with Handle server API.
+// inspired by : https://github.com/theNBS/handleserver-samples
+class HandleClient {
+
+    public function __construct($privateKeyOrCert, $pkpass, $adminId, $handleHost, $handlePort, $httpClient) {
+        $this->session = null;
+        $this->cert = null;
+        $this->adminId = $adminId;
+        $this->privateKeyRes = null;
+        $this->privateKeyOrCert = $privateKeyOrCert;
+        $this->pkpass = $pkpass;
+        $this->handleHost = $handleHost;
+        $this->handlePort = $handlePort;
+        $this->httpClient = $httpClient;
+        $this->guzzleOptions = ['verify' => false,];
+    }
+
+    public function __destruct () {
+        $this->close();
+    }
+
+    public function close() {
+        $this->deleteSession();
+        $this->freeResources();        
+    }
+
+    private function getBaseUrl() {
+        return "https://$this->handleHost:$this->handlePort/api/";
+    }
+
+    private function getSessionAuthHeader() {
+        return "Handle sessionId=$this->session";
+    }
+
+    private function generateClientNonce() {
+        return openssl_random_pseudo_bytes(16);
+    }
+
+    private function getPrivateKeyRes() {
+        if(is_null($this->privateKeyRes)) {
+            $this->privateKeyRes = openssl_pkey_get_private($this->privateKeyOrCert, $this->pkpass);
+        }       
+        return $this->privateKeyRes;
+    }
+
+    private function freeResources() {
+        if(!empty($this->privateKeyRes)) {
+            $privateKeyRes = $this->privateKeyRes;
+            $this->privateKeyRes = null;
+            openssl_free_key($privateKeyRes);
+        }
+    }
+
+    private function signBytesDsa($str) {
+
+        openssl_sign($str, $signature, $this->getPrivateKeyRes(), OPENSSL_ALGO_DSS1);
+
+        return $signature;
+
+    }
+
+    private function signBytesRsa($str) {
+
+        $rsa = new RSA();
+        $rsa->setHash('sha256');
+        if(!empty($this->pkpass)) {
+            $rsa->setPassword($this->pkpass);
+        }
+        $keyContent = $this->privateKeyOrCert;
+        if(is_file($keyContent)) {
+            $keyContent = file_get_contents($keyContent);
+        }
+        $rsa->loadKey($keyContent);
+        $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
+        
+        return $rsa->sign($str);
+
+    }
+
+
+
+    private function createAuthorisationHeaderFromJson($jsonresp) {
+        # Unpick number once (nonce) and session id from server response (this is the challenge)
+        $serverNonce = base64_decode($jsonresp['nonce']);
+        $this->sessionId = $jsonresp['sessionId'];
+
+        # Generate a client number once (cnonce)
+        $clientNonce = $this->generateClientNonce();
+        $clientNonceStr = base64_encode($clientNonce);
+
+        # Our response has to be the signature of server nonce + client nonce
+        $combinedNonce = $serverNonce . $clientNonce;
+        if($this->getPrivateKeyRes() === false) {
+            throw new \Exception("HandleClient: can not read private res");
+        }
+        $keyDetails = openssl_pkey_get_details($this->getPrivateKeyRes());
+        if($keyDetails === false) {
+            throw new \Exception("HandleClient: can not read private key");
+        }
+        if($keyDetails['type']===OPENSSL_KEYTYPE_RSA) {
+            $signature = $this->signBytesRsa($combinedNonce);
+            $signAlg = 'SHA256';
+        } elseif ($keyDetails['type']===OPENSSL_KEYTYPE_DSA) {
+            $signature = $this->signBytesDsa($combinedNonce);
+            $signAlg = 'SHA1';
+        } else {
+            throw new \Exception("HandleClient: $keyDetails[type] Format unknown");
+        }
+        $signatureStr = base64_encode($signature);
+
+        $this->freeResources();
+        # Build the authorisation header to send with the request
+        # Use SHA1 for DSA keys; SHA256 can be used for RSA keys
+        return $this->buildComplexAuthorizationString($signatureStr, $signAlg, $clientNonceStr);
+
+    }
+
+    private function buildComplexAuthorizationString($signatureString, $signAlg, $clientNonceString) {
+        return "Handle " .
+              "version=\"0\", " .
+              "sessionId=\"$this->sessionId\", " .
+              "cnonce=\"$clientNonceString\", " .
+              "id=\"$this->adminId\", " .
+              "type=\"HS_PUBKEY\", " .
+              "alg=\"$signAlg\", " .
+              "signature=\"$signatureString\"";
+    }
+
+    public function initSession() {
+
+        if(!empty($this->session) || !empty($this->cert)) {
+            return;
+        }
+
+        $headers = key_exists('headers', $this->guzzleOptions)?$this->guzzleOptions['headers']:[];
+        $headers = array_merge($headers, [
+            'Content-Type' => 'application/json;charset=UTF-8',
+        ]);
+
+        $certContent = $this->privateKeyOrCert;
+        if(is_file($certContent)) {
+            $certContent = file_get_contents($certContent);
+        }
+        if(openssl_x509_parse($certContent) !== false) {
+            if(!empty($this->pkpass)) {
+                $this->cert = [$this->privateKeyOrCert, $this->pkpass];
+            } else {
+                $this->cert = $this->privateKeyOrCert;
+            }
+            $headers['Authorization'] =  "Handle clientCert=\"true\"";
+        } else {
+            $url = $this->getBaseUrl()."sessions/";
+            $challengeRes = $this->httpClient->post($url, ['verify' => false]);
+
+            $jsonResp = json_decode($challengeRes->getBody(), true);
+            $pkheaders = [
+                'Content-Type' => 'application/json;charset=UTF-8',
+                'Authorization' => $this->createAuthorisationHeaderFromJson($jsonResp)
+            ];
+            # Send the request again with a valid correctly signed Authorization header
+            $sessionResp = $this->httpClient->put($url.'this', ['headers' => $pkheaders, 'verify' => false]);
+            Log::debug('Create session with auth: '.$sessionResp->getStatusCode().' : '.$sessionResp->getReasonPhrase());
+
+            $jsonResp = json_decode($sessionResp->getBody(), true);
+
+            $this->session = $jsonResp['authenticated']?$jsonResp['sessionId']:"";
+            $headers['Authorization'] = "Handle version=\"0\", sessionId=\"$this->session\"";
+
+        }
+
+        $this->guzzleOptions = array_merge($this->guzzleOptions, ['headers' => $headers, 'cert' => $this->cert]);
+
+    }
+
+    // will call a async method. Apart logging we do not really care in the result
+    public function deleteSession() {
+        if(empty($this->session)) {
+            return;
+        }
+        $headers = [
+            'Content-Type' => 'application/json;charset=UTF-8',
+            'Authorization' => $this->getSessionAuthHeader()
+        ];
+        $url = $this->getBaseUrl()."sessions/this";
+        // Do not really care of the response...
+        $this->httpClient->deleteAsync($url, ['headers' => $headers, 'verify' => false]);
+        $this->session = null;
+
+    }
+
+    /**
+     * Paginate all handle as a paginator.
+     *
+     * @param  int  $perPage
+     * @param  string  $pageName
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public function paginateAll($prefix, $perPage = 15, $pageName = 'page', $page = null) {
+
+        $this->initSession();
+
+        $url = $this->getBaseUrl()."handles";
+
+        $params = [
+            'prefix' => $prefix,
+            'page' => is_null($page)?0:$page-1,
+            'pageSize' => $perPage
+        ];
+
+        $paginateRes = $this->httpClient->get($url, array_merge($this->guzzleOptions, ['query' => $params]));
+
+
+        $paginateJson = json_decode($paginateRes->getBody(), true);
+
+        $total = (int)$paginateJson['totalCount'];
+
+        $results = $paginateJson['handles'];
+        
+        return new LengthAwarePaginator($results, $total, $perPage, $page, [
+            'path' => Paginator::resolveCurrentPath(),
+            'pageName' => $pageName,
+        ]);
+   
+    }
+
+    public function deleteHandle($handle) {
+        $this->initSession();
+        
+        $delUrl = $this->getBaseUrl()."handles/$handle";
+        $delRes = $this->httpClient->delete($delUrl, $this->guzzleOptions);
+
+        Log::debug('Delete Handle: '.$delRes->getStatusCode().': '.$delRes->getReasonPhrase());
+    }
+
+    public function createHandleUrlRecord($handle, $url) {
+        $this->initSession();
+        $currentDate = gmstrftime('%Y-%m-%dT%H:%M:%SZ');
+        $handleRecord = [
+          'values' => [
+            ['index' => 1, 'ttl' => 86400, 'type' => 'URL', 'timestamp' => $currentDate, 'data' => ['value'=> $url, 'format'=> 'string']],
+            ['index' => 100, 'ttl' => 86400, 'type' => 'HS_ADMIN', 'timestamp' => $currentDate, 'data' => [
+                'value' => ['index' => 200, 'handle' => $this->adminId],
+                'permissions' => '011111110011',
+                'format' => 'admin'
+              ]
+            ]
+          ],
+          'handle' => $handle,
+        ];
+
+        $submitUrl = $this->getBaseUrl()."handles/$handle";
+
+        $submitRes = $this->httpClient->put($submitUrl, array_merge($this->guzzleOptions, ['json' => $handleRecord, ]));
+
+        Log::debug('Create Handle Url: '.$submitRes->getStatusCode().' : '.$submitRes->getReasonPhrase());
+    }
+
+}
--- a/server/src/composer.json	Wed Apr 13 13:38:40 2016 +0200
+++ b/server/src/composer.json	Fri Apr 22 11:20:17 2016 +0200
@@ -4,17 +4,24 @@
     "keywords": ["framework", "laravel"],
     "license": "MIT",
     "type": "project",
+    "repositories": [
+      {
+        "type": "vcs",
+        "url": "https://github.com/phpseclib/phpseclib"
+      }
+    ],
     "require": {
         "php": ">=5.5.9",
         "laravel/framework": "5.2.*",
         "easyrdf/easyrdf": "dev-master",
         "ml/json-ld": "*",
         "caseyamcl/phpoaipmh": "~2.4",
-        "guzzlehttp/guzzle":   "~6.1",
+        "guzzlehttp/guzzle":   ">=6.1",
         "laravelcollective/html": "5.2.*",
         "shift31/laravel-elasticsearch": "~1.0",
         "asm89/stack-cors": "dev-master as 0.2.2",
-        "barryvdh/laravel-cors": "^0.7.3"
+        "barryvdh/laravel-cors": "^0.7.3",
+        "phpseclib/phpseclib": "^2.0"
     },
     "require-dev": {
         "fzaninotto/faker": "~1.4",
--- a/server/src/composer.lock	Wed Apr 13 13:38:40 2016 +0200
+++ b/server/src/composer.lock	Fri Apr 22 11:20:17 2016 +0200
@@ -4,8 +4,8 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "0f48e638389cfd8e79cc427ecc70d47d",
-    "content-hash": "ba1a992f1f0e32f5b520c06aee48327a",
+    "hash": "bd77aa7395e26dda4693aaf9a7655ec8",
+    "content-hash": "7a38fd5e8db2e507da6db17d237e62ff",
     "packages": [
         {
             "name": "asm89/stack-cors",
@@ -641,16 +641,16 @@
         },
         {
             "name": "guzzlehttp/psr7",
-            "version": "1.2.3",
+            "version": "1.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/psr7.git",
-                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b"
+                "reference": "31382fef2889136415751badebbd1cb022a4ed72"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/psr7/zipball/2e89629ff057ebb49492ba08e6995d3a6a80021b",
-                "reference": "2e89629ff057ebb49492ba08e6995d3a6a80021b",
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/31382fef2889136415751badebbd1cb022a4ed72",
+                "reference": "31382fef2889136415751badebbd1cb022a4ed72",
                 "shasum": ""
             },
             "require": {
@@ -695,7 +695,7 @@
                 "stream",
                 "uri"
             ],
-            "time": "2016-02-18 21:54:00"
+            "time": "2016-04-13 19:56:01"
         },
         {
             "name": "jakub-onderka/php-console-color",
@@ -1205,16 +1205,16 @@
         },
         {
             "name": "monolog/monolog",
-            "version": "1.18.2",
+            "version": "1.19.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Seldaek/monolog.git",
-                "reference": "064b38c16790249488e7a8b987acf1c9d7383c09"
+                "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/064b38c16790249488e7a8b987acf1c9d7383c09",
-                "reference": "064b38c16790249488e7a8b987acf1c9d7383c09",
+                "url": "https://api.github.com/repos/Seldaek/monolog/zipball/5f56ed5212dc509c8dc8caeba2715732abb32dbf",
+                "reference": "5f56ed5212dc509c8dc8caeba2715732abb32dbf",
                 "shasum": ""
             },
             "require": {
@@ -1279,7 +1279,7 @@
                 "logging",
                 "psr-3"
             ],
-            "time": "2016-04-02 13:12:58"
+            "time": "2016-04-12 18:29:35"
         },
         {
             "name": "mtdowling/cron-expression",
@@ -1472,6 +1472,98 @@
             "time": "2016-03-18 20:34:03"
         },
         {
+            "name": "phpseclib/phpseclib",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpseclib/phpseclib.git",
+                "reference": "ba6fb78f727cd09f2a649113b95468019e490585"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/ba6fb78f727cd09f2a649113b95468019e490585",
+                "reference": "ba6fb78f727cd09f2a649113b95468019e490585",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phing/phing": "~2.7",
+                "phpunit/phpunit": "~4.0",
+                "sami/sami": "~2.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "suggest": {
+                "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
+                "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
+                "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
+                "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "phpseclib\\": "phpseclib/"
+                }
+            },
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jim Wigginton",
+                    "email": "terrafrost@php.net",
+                    "role": "Lead Developer"
+                },
+                {
+                    "name": "Patrick Monnerat",
+                    "email": "pm@datasphere.ch",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Andreas Fischer",
+                    "email": "bantu@phpbb.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Hans-Jürgen Petrich",
+                    "email": "petrich@tronic-media.com",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Graham Campbell",
+                    "email": "graham@alt-three.com",
+                    "role": "Developer"
+                }
+            ],
+            "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
+            "homepage": "http://phpseclib.sourceforge.net",
+            "keywords": [
+                "BigInteger",
+                "aes",
+                "asn.1",
+                "asn1",
+                "blowfish",
+                "crypto",
+                "cryptography",
+                "encryption",
+                "rsa",
+                "security",
+                "sftp",
+                "signature",
+                "signing",
+                "ssh",
+                "twofish",
+                "x.509",
+                "x509"
+            ],
+            "support": {
+                "source": "https://github.com/phpseclib/phpseclib/tree/2.0.1",
+                "issues": "https://github.com/phpseclib/phpseclib/issues"
+            },
+            "time": "2016-01-18 17:07:21"
+        },
+        {
             "name": "pimple/pimple",
             "version": "v3.0.2",
             "source": {
@@ -2813,12 +2905,12 @@
             "source": {
                 "type": "git",
                 "url": "https://github.com/padraic/mockery.git",
-                "reference": "f0e9aeb13f6231cc5a16aa78b421d543aae06e71"
+                "reference": "60dcf1a0fe131a5c14350af07f7bc4ace02efbef"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/padraic/mockery/zipball/f0e9aeb13f6231cc5a16aa78b421d543aae06e71",
-                "reference": "f0e9aeb13f6231cc5a16aa78b421d543aae06e71",
+                "url": "https://api.github.com/repos/padraic/mockery/zipball/60dcf1a0fe131a5c14350af07f7bc4ace02efbef",
+                "reference": "60dcf1a0fe131a5c14350af07f7bc4ace02efbef",
                 "shasum": ""
             },
             "require": {
@@ -2870,7 +2962,7 @@
                 "test double",
                 "testing"
             ],
-            "time": "2016-04-12 09:56:12"
+            "time": "2016-04-15 13:15:13"
         },
         {
             "name": "myclabs/deep-copy",
@@ -3380,16 +3472,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "5.3.1",
+            "version": "5.3.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "34a3acb401ae79deb37bc6e5f5ec3d325b369b4c"
+                "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/34a3acb401ae79deb37bc6e5f5ec3d325b369b4c",
-                "reference": "34a3acb401ae79deb37bc6e5f5ec3d325b369b4c",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2c6da3536035617bae3fe3db37283c9e0eb63ab3",
+                "reference": "2c6da3536035617bae3fe3db37283c9e0eb63ab3",
                 "shasum": ""
             },
             "require": {
@@ -3451,7 +3543,7 @@
                 "testing",
                 "xunit"
             ],
-            "time": "2016-04-07 07:04:34"
+            "time": "2016-04-12 16:20:08"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
--- a/server/src/config/corpusparole.php	Wed Apr 13 13:38:40 2016 +0200
+++ b/server/src/config/corpusparole.php	Fri Apr 22 11:20:17 2016 +0200
@@ -137,5 +137,8 @@
         ],
         "contentSecurityPolicyHeader" => "Content-Security-Policy-Report-Only",
         "exportApplicationGlobal" => true
-    ]
+    ],
+
+    'handle_host' => env('HANDLE_HOST'),
+    'handle_port' => env('HANDLE_PORT'),
 ];
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/tests/Libraries/Handle/HandleClientIntegrationTest.php	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,222 @@
+<?php
+
+use Illuminate\Foundation\Testing\WithoutMiddleware;
+use Illuminate\Foundation\Testing\DatabaseMigrations;
+use Illuminate\Foundation\Testing\DatabaseTransactions;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\HandlerStack;
+
+
+use CorpusParole\Libraries\Handle\HandleClient;
+
+class HandleClientIntegrationTest extends TestCase
+{
+
+    public function setUp() {
+        parent::setUp();
+
+        $this->handleHost = env('HANDLE_HOST', null);
+        $this->handlePort = env('HANDLE_PORT', 8000);
+
+        if(empty($this->handleHost)) {
+            $this->markTestSkipped('Handle host empty');
+        }
+
+        $this->testPrefix = env('HANDLE_TEST_PREFIX', null);
+        if(empty($this->testPrefix)) {
+            $this->markTestSkipped('Handle test prefix not defined');
+        }
+
+        $certPem = env('HANDLE_TEST_CERT', null);
+        $this->certPemPassword = env('HANDLE_TEST_CERT_PASSWORD', null);
+        $this->certAdminHandle = env('HANDLE_TEST_CERT_ADMIN_HANDLE', null);
+        if(empty($certPem) || empty($this->certAdminHandle)) {
+            $this->markTestSkipped('Handle test certificate or user is empty');
+        }
+
+        $dsaPem = env('HANDLE_TEST_DSA_KEY', null);
+        $this->dsaPemPassword = env('HANDLE_TEST_DSA_PASSWORD', null);
+        $this->dsaAdminHandle = env('HANDLE_TEST_DSA_ADMIN_HANDLE', null);
+        if(empty($dsaPem) || empty($this->dsaAdminHandle)) {
+            $this->markTestSkipped('Handle dsa key or user not set');
+        }
+        $this->dsaAdminRawHandle = str_replace("300:", "", $this->dsaAdminHandle);
+
+        $rsaPem = env('HANDLE_TEST_RSA_KEY', null);
+        $this->rsaPemPassword = env('HANDLE_TEST_RSA_PASSWORD', null);
+        $this->rsaAdminHandle = env('HANDLE_TEST_RSA_ADMIN_HANDLE', null);
+        if(empty($rsaPem) || empty($this->rsaAdminHandle)) {
+            $this->markTestSkipped('Handle rsa key or user not set');
+        }
+        $this->rsaAdminRawHandle = str_replace("300:", "", $this->rsaAdminHandle);
+
+        $this->filesToDelete = [];
+
+        if(is_file($certPem)) {
+            $this->certTmpPath = $certPem;
+        } else {
+            $this->certTmpPath = tempnam("/tmp", "CERT_TEST");
+            array_push($this->filesToDelete,$this->certTmpPath);
+            file_put_contents($this->certTmpPath, $certPem);
+        }
+
+        if(is_file($dsaPem)) {
+            $this->dsaTmpPath = $dsaPem;
+        } else {
+            $this->dsaTmpPath = tempnam("/tmp", "DSA_TEST");
+            array_push($this->filesToDelete,$this->dsaTmpPath);
+            file_put_contents($this->dsaTmpPath, $dsaPem);
+        }
+
+        if(is_file($rsaPem)) {
+            $this->rsaTmpPath = $rsaPem;
+        } else {
+            $this->rsaTmpPath = tempnam("/tmp", "RSA_TEST");
+            array_push($this->filesToDelete,$this->rsaTmpPath);
+            file_put_contents($this->rsaTmpPath, $rsaPem);
+        }
+
+        // Create a middleware that echoes parts of the request.
+        // $tapMiddleware = Middleware::tap(function ($request) {
+        //     echo "\n+++++++++++++++++\nREQ HEADERS : ".print_r($request->getHeaders(), true)."\n+++++++++++++++++++\n";
+        //     echo "\n------------------\nREQ BODY : ".$request->getBody()."\n-------------------\n";
+        // });
+
+        // $stack = HandlerStack::create();
+        // $stack->push(Middleware::mapResponse(function ($response) {
+        //     echo "\n+++++++++++++++++\nRESP HEADERS : ".print_r($response->getHeaders(), true)."\n+++++++++++++++++++\n";
+        //     echo "\n------------------\nRESP BODY : ".$response->getBody()."\n-------------------\n";
+        //     return $response;
+        // }));
+
+        // $this->httpClient = new Client(['debug' => true, 'handler' => $tapMiddleware($stack)]);
+        $this->httpClient = new Client();
+
+    }
+
+    public function tearDown() {
+        foreach($this->filesToDelete as $ftd) {
+            unlink($ftd);
+        }
+
+        //$this->httpClient->delete("repositories/$this->sesameRepository");
+        parent::tearDown();
+    }
+
+
+    /**
+     * Test Pagination with DSA key.
+     *
+     * @return void
+     */
+    public function testPaginateDSA()
+    {
+        $handleClient = new HandleClient('file://'.$this->dsaTmpPath, $this->dsaPemPassword, $this->dsaAdminHandle, $this->handleHost, $this->handlePort, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertGreaterThanOrEqual(1, $pagination->count(), "Must have at least 1 handle");
+        $this->assertGreaterThanOrEqual(1, $pagination->total(), "Total is at least 1");
+
+    }
+
+
+    /**
+     * Test Pagination with RSA key.
+     *
+     * @return void
+     */
+    public function testPaginateRSA()
+    {
+        $handleClient = new HandleClient('file://'.$this->rsaTmpPath, $this->rsaPemPassword, $this->rsaAdminHandle, $this->handleHost, $this->handlePort, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertGreaterThanOrEqual(1, $pagination->count(), "Must have at least 1 handle");
+        $this->assertGreaterThanOrEqual(1, $pagination->total(), "Total is at least 1");
+
+    }
+
+    /**
+     * Test Pagination with Client Certificate.
+     *
+     * @return void
+     */
+    public function testPaginateCert()
+    {
+        $handleClient = new HandleClient($this->certTmpPath, $this->certPemPassword, $this->certAdminHandle, $this->handleHost, $this->handlePort, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertGreaterThanOrEqual(1, $pagination->count(), "Must have at least 1 handle");
+        $this->assertGreaterThanOrEqual(1, $pagination->total(), "Total is at least 1");
+
+    }
+
+
+    /**
+     * Test Handle creation
+     */
+    public function testCreateHandleUrl() {
+
+        $handleClient = new HandleClient('file://'.$this->rsaTmpPath, $this->rsaPemPassword, $this->rsaAdminHandle, $this->handleHost, $this->handlePort, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+        $totalPrev = $pagination->total();
+
+        $handle = strtoupper(uniqid("$this->testPrefix/TEST_HANDLE_CLIENT_"));
+
+        //$handle = "$this->testPrefix/TEST_HANDLE_CLIENT_";
+
+        $handleClient->createHandleUrlRecord($handle, "http://www.example.com");
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+
+        $this->assertEquals($totalPrev + 1, $pagination->total(), "Must have one more handle");
+
+        $this->assertContains($handle, $pagination);
+
+        $handleClient->deleteHandle($handle);
+
+    }
+
+    /**
+     * Test Handle delete
+     */
+    public function testDeleteHandle() {
+
+        $handleClient = new HandleClient('file://'.$this->rsaTmpPath, $this->rsaPemPassword, $this->rsaAdminHandle, $this->handleHost, $this->handlePort, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+        $totalPrev = $pagination->total();
+
+        $handle = strtoupper(uniqid("$this->testPrefix/TEST_HANDLE_CLIENT_"));
+
+        $handleClient->createHandleUrlRecord($handle, "http://www.example.com");
+        $handleClient->deleteHandle($handle);
+
+
+        $pagination = $handleClient->paginateAll($this->testPrefix);
+
+        $this->assertEquals($totalPrev, $pagination->total(), "Must have one more handle");
+
+        $this->assertNotContains($handle, $pagination);
+
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/server/src/tests/Libraries/Handle/HandleClientTest.php	Fri Apr 22 11:20:17 2016 +0200
@@ -0,0 +1,414 @@
+<?php
+
+use Illuminate\Foundation\Testing\WithoutMiddleware;
+use Illuminate\Foundation\Testing\DatabaseMigrations;
+use Illuminate\Foundation\Testing\DatabaseTransactions;
+
+use GuzzleHttp\Client;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Response;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Exception\RequestException;
+
+use CorpusParole\Libraries\Handle\HandleClient;
+
+class HandleClientTest extends TestCase
+{
+    const CERT_PEM = <<<EOT
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDxemvFVLJTkXl65rTnzSPLtJaP
+NacGD/SYxnhE1l98F6lj/JQaEH094GWGsCWf41XkZOZYMAZEGIDqdmr6vZjhW7zNjGXCA83PMb+r
+p/BOHuq+O5OQaHy9kI2kbNgfqbm+IzP9vrxTXmsVh4N/mCtfygq/uw8S0z0T5G1O9VoJ3mfBQ6dK
+fjqUlHtcp2JPt8gDJQ0a8npQysFOeuX+cVV/KW+M1o53xzIOlufudD0dHV9AK9o/4NHQpCgG0Izl
+6vcpNqRvotzEov03ID586gUw744ViLPqtOvxM54ac6X2ZKjjwhzGPFL6/A/BqPKQg2xExMbRxquN
+E50MZFSL7AVNAgMBAAECggEBAIgWXAIbl1wtUe8HcdBo9dFpdqhCrlxmRYyL3V++CJXgp9/nined
+71cdzVm4j5up/M/4ZTAx8HYT8SMxlVy12SC4GJEmnHpYtnYcLU7KGE2OcvC+loz9tb8HZivSA4Lk
+nf8fy78m7O92+JZrhpoz4c3cCW7ANuB/F57MTyX+c7229zEsiALhURc3Vib6rWUfq3AJBWOEYKoa
+SLLQozi8UJoFODXwC9LDMaxgioNSaU+aj0W0haR/31HRvMQt+Zy1aYYAeuWHT940D6Wgp38ZLMqd
+uj22EY64WhcVfv8lQbIb6jOoZqMIj9erGfWlbGlVxoMhUTGQi0sKmyjFBvClwgECgYEA/Am+vCOf
+WbHKvGd8G8zWk1VRHpJg4huxqn/i18OuYWdojXgfC/HDR4JKIbl/XP+r0HSudTg4vrusZYGrMGZR
+rD82tPDTj24cAI+mfsSNInm6VE4v3eMlEkAcPvqo+qgV5FXU1qFMW5c02vTqOyC/Swjr4YRkP/H7
+XhI8T+C6zkECgYEA9UYuSH4ZOehYEDtSXYK4R5NjhjM38a/bulJ8rUBZ8vtA1UACAkoGYXbPukzE
+GkEnihuFFDtW7KEY35q2ATj5zV1n1Eh3kfJ7TURl0hNva63jf/uNIXbS0JaFBh8/0N0tkJYrwCOH
+aKjAcaMs6OEMuqiLeJX/lpGdCLy+bdikjA0CgYEAjb0NPtDptI9/7CRmb1vq8EBytjartXzNlY0G
+DUFGuBBng+ehzh3QLE7hUcX4ElzWSwzTb6iGgP0aHpulojpog+2o3mjxESBjLw2aHIB8vn6mgvLV
+iHtNRc44FFLy5MPSS+7ZGzrqrjeCygAiGc54xcHmpZTGrA9xxZ2LvYAsjgECgYBHpKtKQ4MM1iA7
+BYjmhZNcXOH6RiW2M/8Dqqp+VMixX9CtOZgcoRDCEVlV+qxhWty4y5cQtNCMJ/ZDAkbdjo3w/bKh
+YFWEzQZVKIBxX3nxQwar0IeF2dZ0VTMf+arDUIfJrwOyiO45lOk5iEdK8smsECVFINNaYMg00CTz
+ow9VVQKBgFDCVC97xsu5lOl6nd9mSu2GL+6hzpWPIZDD2SwMs6Vkkj33UhqyqziCOFKmRTF5oON+
+ew2EGVPukWZsYXNH0KLwdJ7q0QKEONGPMxVNURkHR9cJ8ScaZkiZ6Zh0MT3S3kTak+A5pzo3eS4H
+LhgZ72DZ0ABBEGoMfaVh8fJHZKcm
+-----END PRIVATE KEY-----
+-----BEGIN CERTIFICATE-----
+MIIDVTCCAj2gAwIBAgIJAMa/cIGB8QEVMA0GCSqGSIb3DQEBCwUAMCUxIzAhBgNV
+BAMUGjMwMDoxMTI4MC4xMDAvQ09SUFVTX0FETUlOMB4XDTE2MDQxODEyMDg1M1oX
+DTE2MDUxODEyMDg1M1owJTEjMCEGA1UEAxQaMzAwOjExMjgwLjEwMC9DT1JQVVNf
+QURNSU4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDxemvFVLJTkXl6
+5rTnzSPLtJaPNacGD/SYxnhE1l98F6lj/JQaEH094GWGsCWf41XkZOZYMAZEGIDq
+dmr6vZjhW7zNjGXCA83PMb+rp/BOHuq+O5OQaHy9kI2kbNgfqbm+IzP9vrxTXmsV
+h4N/mCtfygq/uw8S0z0T5G1O9VoJ3mfBQ6dKfjqUlHtcp2JPt8gDJQ0a8npQysFO
+euX+cVV/KW+M1o53xzIOlufudD0dHV9AK9o/4NHQpCgG0Izl6vcpNqRvotzEov03
+ID586gUw744ViLPqtOvxM54ac6X2ZKjjwhzGPFL6/A/BqPKQg2xExMbRxquNE50M
+ZFSL7AVNAgMBAAGjgYcwgYQwHQYDVR0OBBYEFLycwnSTnQGcssXGrj8TGn2rSAgD
+MFUGA1UdIwROMEyAFLycwnSTnQGcssXGrj8TGn2rSAgDoSmkJzAlMSMwIQYDVQQD
+FBozMDA6MTEyODAuMTAwL0NPUlBVU19BRE1JToIJAMa/cIGB8QEVMAwGA1UdEwQF
+MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGcQGNpACyRBwBQ/iXy7RxLSGdhvbGmh
+bikNjBYJVAnYSZjIY2keF1BnhJvaqWHiixoeXC0J9gmN+caZ/WcCyW7xie7fBbn2
+FYYJDUj1ik4hD7Cptvd+YwNu2ZjGOKoOqVghGaZSL5cWcfYNYfwv3gQVj05CG6oq
+pHHeD5QJw/o/2LYJuFRymPA2AG96mjUSH2gCsqnSxPudfJYVVzjlkWbehIt8ss8l
+SdSJqhGvvC5G6x9WhVC3qw56n65+g1FIbhFCXddG/1WGmSfmE8hwlvaOoYgynQGD
+BNpIg6V6Mo5EZWnBOuyK26xy4BgXiQCdJPejLGmmzSAUXNAQx47R7VI=
+-----END CERTIFICATE-----
+EOT;
+
+    const DSA_PEM = <<<EOT
+-----BEGIN PRIVATE KEY-----
+MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCPeTXZuarpv6vtiHrPSVG28y7FnjuvNxjo6sSW
+Hz79NgbnQ1GpxBgzObgJ58KuHFObp0dbhdARrbi0eYd1SYRpXKwOjxSzNggooi/6JxEKPWKpk0U0
+CaD+aWxGWPhL3SCBnDcJoBBXsZWtzQAjPbpUhLYpH51kjviDRIZ3l5zsBLQ0pqwudemYXeI9sCkv
+wRGMn/qdgYHnM423krcw17njSVkvaAmYchU5Feo9a4tGU8YzRY+AOzKkwuDycpAlbk4/ijsIOKHE
+UOThjBopo33fXqFD3ktm/wSQPtXPFiPhWNSHxgjpfyEc2B3KI8tuOAdl+CLjQr5ITAV2OTlgHNZn
+Ah0AuvaWpoV499/e5/pnyXfHhe8ysjO65YDAvNVpXQKCAQAWplxYIEhQcE51AqOXVwQNNNo6NHjB
+VNTkpcAtJC7gT5bmHkvQkEq9rI837rHgnzGC0jyQQ8tkL4gAQWDt+coJsyB2p5wypifyRz6Rh5ui
+xOdEvSCBVEy1W4AsNo0fqD7UielOD6BojjJCilx4xHjGjQUntxyaOrsLC+EsRGiWOefTznTbEBpl
+qiuH9kxoJts+xy9LVZmDS7TtsC98kOmkltOlXVNb6/xF1PYZ9j897buHOSXC8iTgdzEpbaiH7B5H
+SPh++1/et1SEMWsiMt7lU92vAhErDR8C2jCXMiT+J67ai51LKSLZuovjntnhA6Y8UoELxoi34u1D
+FuHvF9veBB4CHHTW1E//VJ4Ms6LhUNp9nY8IgcxN6upRxLvE+uE=
+-----END PRIVATE KEY-----
+EOT;
+
+    const RSA_PEM = <<<EOT
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDxemvFVLJTkXl65rTnzSPLtJaP
+NacGD/SYxnhE1l98F6lj/JQaEH094GWGsCWf41XkZOZYMAZEGIDqdmr6vZjhW7zNjGXCA83PMb+r
+p/BOHuq+O5OQaHy9kI2kbNgfqbm+IzP9vrxTXmsVh4N/mCtfygq/uw8S0z0T5G1O9VoJ3mfBQ6dK
+fjqUlHtcp2JPt8gDJQ0a8npQysFOeuX+cVV/KW+M1o53xzIOlufudD0dHV9AK9o/4NHQpCgG0Izl
+6vcpNqRvotzEov03ID586gUw744ViLPqtOvxM54ac6X2ZKjjwhzGPFL6/A/BqPKQg2xExMbRxquN
+E50MZFSL7AVNAgMBAAECggEBAIgWXAIbl1wtUe8HcdBo9dFpdqhCrlxmRYyL3V++CJXgp9/nined
+71cdzVm4j5up/M/4ZTAx8HYT8SMxlVy12SC4GJEmnHpYtnYcLU7KGE2OcvC+loz9tb8HZivSA4Lk
+nf8fy78m7O92+JZrhpoz4c3cCW7ANuB/F57MTyX+c7229zEsiALhURc3Vib6rWUfq3AJBWOEYKoa
+SLLQozi8UJoFODXwC9LDMaxgioNSaU+aj0W0haR/31HRvMQt+Zy1aYYAeuWHT940D6Wgp38ZLMqd
+uj22EY64WhcVfv8lQbIb6jOoZqMIj9erGfWlbGlVxoMhUTGQi0sKmyjFBvClwgECgYEA/Am+vCOf
+WbHKvGd8G8zWk1VRHpJg4huxqn/i18OuYWdojXgfC/HDR4JKIbl/XP+r0HSudTg4vrusZYGrMGZR
+rD82tPDTj24cAI+mfsSNInm6VE4v3eMlEkAcPvqo+qgV5FXU1qFMW5c02vTqOyC/Swjr4YRkP/H7
+XhI8T+C6zkECgYEA9UYuSH4ZOehYEDtSXYK4R5NjhjM38a/bulJ8rUBZ8vtA1UACAkoGYXbPukzE
+GkEnihuFFDtW7KEY35q2ATj5zV1n1Eh3kfJ7TURl0hNva63jf/uNIXbS0JaFBh8/0N0tkJYrwCOH
+aKjAcaMs6OEMuqiLeJX/lpGdCLy+bdikjA0CgYEAjb0NPtDptI9/7CRmb1vq8EBytjartXzNlY0G
+DUFGuBBng+ehzh3QLE7hUcX4ElzWSwzTb6iGgP0aHpulojpog+2o3mjxESBjLw2aHIB8vn6mgvLV
+iHtNRc44FFLy5MPSS+7ZGzrqrjeCygAiGc54xcHmpZTGrA9xxZ2LvYAsjgECgYBHpKtKQ4MM1iA7
+BYjmhZNcXOH6RiW2M/8Dqqp+VMixX9CtOZgcoRDCEVlV+qxhWty4y5cQtNCMJ/ZDAkbdjo3w/bKh
+YFWEzQZVKIBxX3nxQwar0IeF2dZ0VTMf+arDUIfJrwOyiO45lOk5iEdK8smsECVFINNaYMg00CTz
+ow9VVQKBgFDCVC97xsu5lOl6nd9mSu2GL+6hzpWPIZDD2SwMs6Vkkj33UhqyqziCOFKmRTF5oON+
+ew2EGVPukWZsYXNH0KLwdJ7q0QKEONGPMxVNURkHR9cJ8ScaZkiZ6Zh0MT3S3kTak+A5pzo3eS4H
+LhgZ72DZ0ABBEGoMfaVh8fJHZKcm
+-----END PRIVATE KEY-----
+
+EOT;
+
+    public function setUp() {
+        parent::setUp();
+
+        // $this->markTestSkipped('Not ready yet');
+
+        $this->certTmpPath = tempnam("/tmp", "CERT_TEST");
+        file_put_contents($this->certTmpPath, HandleClientTest::CERT_PEM);
+
+    }
+
+    public function tearDown() {
+        unlink($this->certTmpPath);
+
+        parent::tearDown();
+    }
+
+    private function initClient($respArray) {
+
+        $this->history = [];
+
+        $mock = new MockHandler($respArray);
+        $handler = HandlerStack::create($mock);
+
+        $history = Middleware::history($this->history);
+
+        $handler->push($history);
+
+        $this->httpClient = new Client(['handler' => $handler]);
+
+    }
+
+
+    /**
+     * Test Pagination with DSA key.
+     *
+     * @return void
+     */
+    public function testPaginateDSA()
+    {
+        $responses = [
+            new Response(201,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8',
+                           'Vary' => "Accept-Encoding"
+                         ],
+                         "{\"sessionId\":\"fo79koatgsdyfjkx4c6p3l0c\",\"nonce\":\"10hYzYGE5vf0a1F9eWfk6g==\"}"
+            ),
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"sessionId\":\"fo79koatgsdyfjkx4c6p3l0c\",\"nonce\":\"10hYzYGE5vf0a1F9eWfk6g==\",\"authenticated\":true,\"id\":\"300:11280.101/CORPUS_ADMIN_DSA\"}"
+            ),
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"2\",\"page\":0,\"pageSize\":15,\"handles\":[\"11280.101/CORPUS_ADMIN\",\"11280.101/CORPUS_ADMIN_DSA\"]}"
+            )
+
+        ];
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient(HandleClientTest::DSA_PEM, null, "300:11281.100/CORPUS_ADMIN_DSA", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101');
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(2, $pagination->count(), "Must have only one handle");
+        $this->assertEquals(2, $pagination->total(), "Total is one");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/CORPUS_ADMIN','11280.101/CORPUS_ADMIN_DSA'], $handles, "Same handles");
+
+        $this->assertCount(3, $this->history, "Must have 3 transactions");
+
+    }
+
+
+    /**
+     * Test Pagination with RSA key.
+     *
+     * @return void
+     */
+    public function testPaginateRSA()
+    {
+        $responses = [
+            new Response(201,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8',
+                           'Vary' => "Accept-Encoding"
+                         ],
+                         "{\"sessionId\":\"fo79koatgsdyfjkx4c6p3l0c\",\"nonce\":\"10hYzYGE5vf0a1F9eWfk6g==\"}"
+            ),
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"sessionId\":\"fo79koatgsdyfjkx4c6p3l0c\",\"nonce\":\"10hYzYGE5vf0a1F9eWfk6g==\",\"authenticated\":true,\"id\":\"300:11280.101/CORPUS_ADMIN\"}"
+            ),
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"2\",\"page\":0,\"pageSize\":15,\"handles\":[\"11280.101/CORPUS_ADMIN\",\"11280.101/CORPUS_ADMIN_DSA\"]}"
+            )
+
+        ];
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient(HandleClientTest::RSA_PEM, null, "300:11280.101/CORPUS_ADMIN", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101');
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(2, $pagination->count(), "Must have only 2 handle");
+        $this->assertEquals(2, $pagination->total(), "Total is 2");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/CORPUS_ADMIN', '11280.101/CORPUS_ADMIN_DSA'], $handles, "Same handles");
+
+        $this->assertCount(3, $this->history, "Must have 3 transactions");
+
+    }
+
+    /**
+     * Test Pagination with Client Certificate.
+     *
+     * @return void
+     */
+    public function testPaginateCert()
+    {
+
+        $responses = [
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"2\",\"page\":0,\"pageSize\":15,\"handles\":[\"11280.101/CORPUS_ADMIN\",\"11280.101/CORPUS_ADMIN_DSA\"]}"
+            )
+        ];
+
+
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient($this->certTmpPath, null, "300:11280.101/CORPUS_ADMIN", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101');
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(2, $pagination->count(), "Must have only 2 handle");
+        $this->assertEquals(2, $pagination->total(), "Total is 2");
+        $this->assertFalse($pagination->hasPages(), "Do not have a page");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/CORPUS_ADMIN', '11280.101/CORPUS_ADMIN_DSA'], $handles, "Same handles");
+
+        $this->assertCount(1, $this->history, "Must have 1 transactions");
+
+    }
+
+
+    /**
+     * Test Pagination with Client Certificate.
+     *
+     * @return void
+     */
+    public function testPaginateFirstPageCert()
+    {
+
+        $responses = [
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"12\",\"page\":0,\"pageSize\":5,\"handles\":[\"11280.101/TEST1\",\"11280.101/TEST2\",\"11280.101/TEST3\",\"11280.101/TEST4\",\"11280.101/TEST5\"]}"
+            )
+        ];
+
+
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient($this->certTmpPath, null, "300:11280.101/CORPUS_ADMIN", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101', 5);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(5, $pagination->count(), "Must have only 5 handle");
+        $this->assertEquals(5, $pagination->perPage(), "Per page is 5");
+        $this->assertEquals(12, $pagination->total(), "Total is 12");
+        $this->assertEquals(1, $pagination->currentPage(), "Current page is 1");
+        $this->assertEquals(3, $pagination->lastPage(), "Last page is 3");
+        $this->assertTrue($pagination->hasMorePages(), "Must have more page");
+        $this->assertTrue($pagination->hasPages(), "Have at least page");
+        $this->assertEquals(1, $pagination->firstItem(), "First item is 1");
+        $this->assertEquals(5, $pagination->lastItem(), "First item is 4");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/TEST1', '11280.101/TEST2', '11280.101/TEST3', '11280.101/TEST4', '11280.101/TEST5'], $handles, "Same handles");
+
+
+    }
+
+    /**
+     * Test Pagination with Client Certificate.
+     *
+     * @return void
+     */
+    public function testPaginateNextPageCert()
+    {
+
+        $responses = [
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"12\",\"page\":1,\"pageSize\":5,\"handles\":[\"11280.101/TEST6\",\"11280.101/TEST7\",\"11280.101/TEST8\",\"11280.101/TEST9\",\"11280.101/TEST10\"]}"
+            )
+        ];
+
+
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient($this->certTmpPath, null, "300:11280.101/CORPUS_ADMIN", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101', 5, 'page', 2);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(5, $pagination->count(), "Must have only 5 handle");
+        $this->assertEquals(5, $pagination->perPage(), "Per page is 5");
+        $this->assertEquals(12, $pagination->total(), "Total is 12");
+        $this->assertEquals(2, $pagination->currentPage(), "Current page is 2");
+        $this->assertEquals(3, $pagination->lastPage(), "Last page is 3");
+        $this->assertTrue($pagination->hasPages(), "Have at least page");
+        $this->assertTrue($pagination->hasMorePages(), "Must have more page");
+        $this->assertEquals(6, $pagination->firstItem(), "First item is 1");
+        $this->assertEquals(10, $pagination->lastItem(), "First item is 4");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/TEST6', '11280.101/TEST7', '11280.101/TEST8', '11280.101/TEST9', '11280.101/TEST10'], $handles, "Same handles");
+
+
+    }
+
+    /**
+     * Test Pagination with Client Certificate.
+     *
+     * @return void
+     */
+    public function testPaginateLastPageCert()
+    {
+
+        $responses = [
+            new Response(200,
+                         [ 'Location' => '/api/sessions/this',
+                           'Content-Type' => 'application/json;charset=UTF-8'
+                         ],
+                         "{\"responseCode\":1,\"prefix\":\"11280.101\",\"totalCount\":\"12\",\"page\":2,\"pageSize\":5,\"handles\":[\"11280.101/TEST11\",\"11280.101/TEST12\"]}"
+            )
+        ];
+
+
+        $this->initClient($responses);
+
+        $handleClient = new HandleClient($this->certTmpPath, null, "300:11280.101/CORPUS_ADMIN", "172.16.1.6", 8000, $this->httpClient);
+
+        $pagination = $handleClient->paginateAll('11280.101', 5, 'page', 3);
+
+        $this->assertNotNull($pagination);
+
+        $this->assertInstanceOf("Illuminate\Pagination\LengthAwarePaginator", $pagination, "Must be a LengthAwarePaginator");
+
+        $this->assertEquals(2, $pagination->count(), "Must have only 2 handle");
+        $this->assertEquals(5, $pagination->perPage(), "Per page is 5");
+        $this->assertEquals(12, $pagination->total(), "Total is 12");
+        $this->assertEquals(3, $pagination->currentPage(), "Current page is 3");
+        $this->assertEquals(3, $pagination->lastPage(), "Last page is 3");
+        $this->assertFalse($pagination->hasMorePages(), "Must have more page");
+        $this->assertTrue($pagination->hasPages(), "Have at least page");
+        $this->assertEquals(11, $pagination->firstItem(), "First item is 1");
+        $this->assertEquals(12, $pagination->lastItem(), "First item is 4");
+
+        $handles = iterator_to_array($pagination);
+        $this->assertEquals(['11280.101/TEST11', '11280.101/TEST12'], $handles, "Same handles");
+
+
+    }
+
+
+}