diff --git a/composer.json b/composer.json
index 49d85f8f5ff1950cfbad208a5567d169b5da4cc8..a837f10218ea42537d30ca92852e9ab6042e7e62 100644
--- a/composer.json
+++ b/composer.json
@@ -44,7 +44,6 @@
         "jaimeperez/twig-configurable-i18n": "^1.2"
     },
     "require-dev": {
-        "ext-pdo_sqlite": "*",
         "phpunit/phpunit": "~4.8.35",
         "mikey179/vfsStream": "~1.6",
         "friendsofphp/php-cs-fixer": "^2.2"
diff --git a/modules/core/lib/Storage/SQLPermanentStorage.php b/modules/core/lib/Storage/SQLPermanentStorage.php
index 54bb5642bf1b59201b850164f1ac343489d4b64a..8788fd7ccb15f828b683e00caf5d6b2b7b4316d8 100644
--- a/modules/core/lib/Storage/SQLPermanentStorage.php
+++ b/modules/core/lib/Storage/SQLPermanentStorage.php
@@ -9,198 +9,209 @@
  * @author Andreas Ă…kre Solberg <andreas@uninett.no>, UNINETT AS.
  * @package SimpleSAMLphp
  */
-class sspmod_core_Storage_SQLPermanentStorage {
-	
-	private $db;
-	
-	function __construct($name, $config = NULL) {
-		if (is_null($config))
-			$config = SimpleSAML_Configuration::getInstance();
-		
-		$datadir = $config->getPathValue('datadir', 'data/');
-		
-		if (!is_dir($datadir))
-			throw new Exception('Data directory [' . $datadir. '] does not exist');
-		if (!is_writable($datadir))
-			throw new Exception('Data directory [' . $datadir. '] is not writable');
-		
-		$sqllitedir = $datadir . 'sqllite/';
-		if (!is_dir($sqllitedir)) {
-			mkdir($sqllitedir);
-		}
-		
-		$dbfile = $sqllitedir . $name . '.sqllite';
-		
-		if ($this->db = new SQLiteDatabase($dbfile)) {
-			$q = @$this->db->query('SELECT key1 FROM data LIMIT 1');
-			if ($q === false) {
-				$this->db->queryExec('
-		CREATE TABLE data (
-			key1 text, 
-			key2 text,
-			type text,
-			value text,
-			created timestamp,
-			updated timestamp,
-			expire timestamp,
-			PRIMARY KEY (key1,key2,type)
-		);
-		');
-			} 
-		} else {
-		    throw new Exception('Error creating SQL lite database [' . $dbfile . '].');
-		}
-	}
-
-	public function set($type, $key1, $key2, $value, $duration = NULL) {
-		if ($this->exists($type, $key1, $key2)) {
-			$this->update($type, $key1, $key2, $value, $duration);
-		} else {
-			$this->insert($type, $key1, $key2, $value, $duration);
-		}
-	}
-
-	private function insert($type, $key1, $key2, $value, $duration = NULL) {
-		
-		$setDuration = '';
-		if (is_null($duration)) {
-			$setDuration = 'NULL';
-		} else {
-			$setDuration = "'" . sqlite_escape_string(time() + $duration) . "'";
-		}
-		
-		$query = "INSERT INTO data (key1,key2,type,created,updated,expire,value) VALUES (" . 
-			"'" . sqlite_escape_string($key1) . "'," . 
-			"'" . sqlite_escape_string($key2) . "'," . 
-			"'" . sqlite_escape_string($type) . "'," . 
-			"'" . sqlite_escape_string(time()) . "'," . 
-			"'" . sqlite_escape_string(time()) . "'," . 
-			$setDuration . "," .
-			"'" . sqlite_escape_string(serialize($value)) . "')";
-		$results = $this->db->queryExec($query);
-		return $results;
-	}
-	
-	private function update($type, $key1, $key2, $value, $duration = NULL) {
-		
-		$setDuration = '';
-		if (is_null($duration)) {
-			$setDuration = ", expire = NULL ";
-		} else {
-			$setDuration = ", expire = '" . sqlite_escape_string(time() + $duration) . "' ";
-		}
-		
-		$query = "UPDATE data SET " . 
-			"updated = '" . sqlite_escape_string(time()) . "'," . 
-			"value = '" . sqlite_escape_string(serialize($value)) . "'" .
-			$setDuration .
-			"WHERE " . 
-			"key1 = '" . sqlite_escape_string($key1) . "' AND " . 
-			"key2 = '" . sqlite_escape_string($key2) . "' AND " . 
-			"type = '" . sqlite_escape_string($type) . "'";
-		$results = $this->db->queryExec($query);
-		return $results;
-	}
-
-	public function get($type = NULL, $key1 = NULL, $key2 = NULL) {
-		
-		$condition = self::getCondition($type, $key1, $key2);
-		$query = "SELECT * FROM data WHERE " . $condition;
-		$results = $this->db->arrayQuery($query, SQLITE_ASSOC);
-
-		if (count($results) !== 1) return NULL;
-		
-		$res = $results[0];
-		$res['value'] = unserialize($res['value']);
-		return $res;
-	}
-	
-	/*
-	 * Return the value directly (not in a container)
-	 */
-	public function getValue($type = NULL, $key1 = NULL, $key2 = NULL) {
-		$res = $this->get($type, $key1, $key2);
-		if ($res === NULL) return NULL;
-		return $res['value'];
-	}
-	
-	public function exists($type, $key1, $key2) {
-		$query = "SELECT * FROM data WHERE " . 
-			"key1 = '" . sqlite_escape_string($key1) . "' AND " . 
-			"key2 = '" . sqlite_escape_string($key2) . "' AND " . 
-			"type = '" . sqlite_escape_string($type) . "' LIMIT 1";
-		$results = $this->db->arrayQuery($query, SQLITE_ASSOC);
-		return (count($results) == 1);
-	}
-		
-	public function getList($type = NULL, $key1 = NULL, $key2 = NULL) {
-		
-		$condition = self::getCondition($type, $key1, $key2);
-		$query = "SELECT * FROM data WHERE " . $condition;
-		$results = $this->db->arrayQuery($query, SQLITE_ASSOC);
-		if (count($results) == 0) return NULL;
-		
-		foreach($results AS $key => $value) {
-			$results[$key]['value'] = unserialize($results[$key]['value']);
-		}
-		return $results;
-	}
-	
-	public function getKeys($type = NULL, $key1 = NULL, $key2 = NULL, $whichKey = 'type') {
-
-		if (!in_array($whichKey, array('key1', 'key2', 'type'), true))
-			throw new Exception('Invalid key type');
-			
-		$condition = self::getCondition($type, $key1, $key2);
-		
-		$query = "SELECT DISTINCT " . $whichKey . " FROM data WHERE " . $condition;
-		$results = $this->db->arrayQuery($query, SQLITE_ASSOC);
-
-		if (count($results) == 0) return NULL;
-		
-		$resarray = array();
-		foreach($results AS $key => $value) {
-			$resarray[] = $value[$whichKey];
-		}
-		
-		return $resarray;
-	}
-	
-	
-	public function remove($type, $key1, $key2) {
-		$query = "DELETE FROM data WHERE " . 
-			"key1 = '" . sqlite_escape_string($key1) . "' AND " . 
-			"key2 = '" . sqlite_escape_string($key2) . "' AND " . 
-			"type = '" . sqlite_escape_string($type) . "'";
-		$results = $this->db->arrayQuery($query, SQLITE_ASSOC);
-		return (count($results) == 1);
-	}
-	
-	public function removeExpired() {
-		$query = "DELETE FROM data WHERE expire NOT NULL AND expire < " . time();
-		$this->db->arrayQuery($query, SQLITE_ASSOC);
-		$changes = $this->db->changes();
-		return $changes;
-	}
-	
-	
-	/**
-	 * Create a SQL condition statement based on parameters
-	 */
-	private static function getCondition($type = NULL, $key1 = NULL, $key2 = NULL) {
-		$conditions = array();
-		
-		if (!is_null($type)) $conditions[] = "type = '" . sqlite_escape_string($type) . "'";
-		if (!is_null($key1)) $conditions[] = "key1 = '" . sqlite_escape_string($key1) . "'";
-		if (!is_null($key2)) $conditions[] = "key2 = '" . sqlite_escape_string($key2) . "'";
-		
-		if (count($conditions) === 0) return '1';
-		
-		$condition = join(' AND ', $conditions);
-		
-		return $condition;
-	}
-	
-	
+class sspmod_core_Storage_SQLPermanentStorage
+{
+    private $db;
+
+    public function __construct($name, $config = null)
+    {
+        if (is_null($config)) {
+            $config = SimpleSAML_Configuration::getInstance();
+        }
+
+        $datadir = $config->getPathValue('datadir', 'data/');
+
+        if (!is_dir($datadir)) {
+            throw new Exception('Data directory ['.$datadir.'] does not exist');
+        } else if (!is_writable($datadir)) {
+            throw new Exception('Data directory ['.$datadir.'] is not writable');
+        }
+
+        $sqllitedir = $datadir.'sqllite/';
+        if (!is_dir($sqllitedir)) {
+            mkdir($sqllitedir);
+        }
+
+        $dbfile = 'sqlite:'.$sqllitedir.$name.'.sqlite';
+        if ($this->db = new \PDO($dbfile)) {
+            $q = @$this->db->query('SELECT key1 FROM data LIMIT 1');
+            if ($q === false) {
+                $this->db->exec('
+		    CREATE TABLE data (
+                        key1 text, 
+                        key2 text,
+                        type text,
+                        value text,
+                        created timestamp,
+                        updated timestamp,
+                        expire timestamp,
+                        PRIMARY KEY (key1,key2,type)
+                    );
+                ');
+            } 
+        } else {
+            throw new Exception('Error creating SQL lite database ['.$dbfile.'].');
+        }
+    }
+
+    public function set($type, $key1, $key2, $value, $duration = null)
+    {
+        if ($this->exists($type, $key1, $key2)) {
+            $this->update($type, $key1, $key2, $value, $duration);
+        } else {
+            $this->insert($type, $key1, $key2, $value, $duration);
+        }
+    }
+
+    private function insert($type, $key1, $key2, $value, $duration = null)
+    {
+        $expire = is_null($duration) ? null : (time() + $duration);
+
+        $query = "INSERT INTO data (key1, key2, type, created, updated, expire, value)".
+            " VALUES(:key1, :key2, :type, :created, :updated, :expire, :value)";
+        $prepared = $this->db->prepare($query);
+        $data = array(':key1' => $key1, ':key2' => $key2,
+            ':type' => $type, ':created' => time(),
+            ':updated' => time(), ':expire' => $expire,
+            ':value' => serialize($value));
+        $prepared->execute($data);
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        return $results;
+    }
+
+    private function update($type, $key1, $key2, $value, $duration = null)
+    {
+        $expire = is_null($duration) ? null : (time() + $duration);
+
+        $query = "UPDATE data SET updated = :updated, value = :value, expire = :expire WHERE key1 = :key1 AND key2 = :key2 AND type = :type";
+        $prepared = $this->db->prepare($query);
+        $data = array(':key1' => $key1, ':key2' => $key2,
+            ':type' => $type, ':updated' => time(),
+            ':expire' => $expire, ':value' => serialize($value));
+        $prepared->execute($data);
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        return $results;
+    }
+
+    public function get($type = null, $key1 = null, $key2 = null)
+    {
+        $conditions = self::getCondition($type, $key1, $key2);
+        $query = 'SELECT * FROM data WHERE '.$conditions;
+
+        $prepared = $this->db->prepare($query);
+        $prepared->execute();
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        if (count($results) !== 1) {
+            return null;
+        }
+
+        $res = $results[0];
+        $res['value'] = unserialize($res['value']);
+        return $res;
+    }
+
+    /*
+     * Return the value directly (not in a container)
+     */
+    public function getValue($type = null, $key1 = null, $key2 = null)
+    {
+        $res = $this->get($type, $key1, $key2);
+        if ($res === null) {
+            return null;
+        }
+        return $res['value'];
+    }
+
+    public function exists($type, $key1, $key2)
+    {
+        $query = 'SELECT * FROM data WHERE type = :type AND key1 = :key1 AND key2 = :key2 LIMIT 1';
+        $prepared = $this->db->prepare($query);
+        $data = array(':type' => $type, ':key1' => $key1, ':key2' => $key2);
+        $prepared->execute($data);
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        return (count($results) == 1);
+    }
+
+    public function getList($type = null, $key1 = null, $key2 = null)
+    {
+        $conditions = self::getCondition($type, $key1, $key2);
+        $query = 'SELECT * FROM data WHERE '.$conditions;
+        $prepared = $this->db->prepare($query);
+        $prepared->execute();
+
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        if (count($results) == 0) {
+            return null;
+        }
+
+        foreach ($results as $key => $value) {
+            $results[$key]['value'] = unserialize($results[$key]['value']);
+        }
+        return $results;
+    }
+
+    public function getKeys($type = null, $key1 = null, $key2 = null, $whichKey = 'type')
+    {
+        if (!in_array($whichKey, array('key1', 'key2', 'type'), true)) {
+            throw new Exception('Invalid key type');
+        }
+
+        $conditions = self::getCondition($type, $key1, $key2);
+        $query = 'SELECT DISTINCT :whichKey FROM data WHERE '.$conditions;
+        $prepared = $this->db->prepare($query);
+        $data = array('whichKey' => $whichKey);
+        $prepared->execute($data);
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+
+        if (count($results) == 0) {
+            return null;
+        }
+
+        $resarray = array();
+        foreach ($results as $key => $value) {
+            $resarray[] = $value[$whichKey];
+        }
+        return $resarray;
+    }
+
+    public function remove($type, $key1, $key2)
+    {
+        $query = 'DELETE FROM data WHERE type = :type AND key1 = :key1 AND key2 = :key2';
+        $prepared = $this->db->prepare($query);
+        $data = array(':type' => $type, ':key1' => $key1, ':key2' => $key2);
+        $prepared->execute($data);
+        $results = $prepared->fetchAll(PDO::FETCH_ASSOC);
+        return (count($results) == 1);
+    }
+
+    public function removeExpired()
+    {
+        $query = "DELETE FROM data WHERE expire NOT NULL AND expire < :expire";
+        $prepared = $this->db->prepare($query);
+        $data = array(':expire' => time());
+        $prepared->execute($data);
+        return $prepared->rowCount();
+    }
+
+    /**
+     * Create a SQL condition statement based on parameters
+     */
+    private function getCondition($type = null, $key1 = null, $key2 = null)
+    {
+        $conditions = array();
+        if (!is_null($type)) {
+            $conditions[] = "type = ".$this->db->quote($type);
+        }
+        if (!is_null($key1)) {
+            $conditions[] = "key1 = ".$this->db->quote($key1);
+        }
+        if (!is_null($key2)) {
+            $conditions[] = "key2 = ".$this->db->quote($key2);
+        }
+        if (count($conditions) === 0) {
+            return '1';
+        }
+        return join(' AND ', $conditions);
+    }
 }
 
diff --git a/tests/modules/core/lib/Storage/SQLPermanentStorageTest.php b/tests/modules/core/lib/Storage/SQLPermanentStorageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e1071050440000315d29522754401565964ed76
--- /dev/null
+++ b/tests/modules/core/lib/Storage/SQLPermanentStorageTest.php
@@ -0,0 +1,84 @@
+<?php
+
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test for the SQLPermanentStorage class.
+ */
+class Test_Core_Storage_SQLPermanentStorage extends TestCase
+{
+    private static $sql;
+
+    public static function setUpBeforeClass()
+    {
+        // Create instance
+        $config = \SimpleSAML_Configuration::loadFromArray([
+            'datadir' => sys_get_temp_dir(),
+        ]);
+        self::$sql = new sspmod_core_Storage_SQLPermanentStorage('test', $config);
+    }
+
+    public static function tearDownAfterClass()
+    {
+        self::$sql = null;
+        unlink(sys_get_temp_dir().'/sqllite/test.sqlite');
+    }
+
+    public function testSet()
+    {
+        // Set a new value
+        self::$sql->set('testtype', 'testkey1', 'testkey2', 'testvalue', 0);
+
+        // Test getCondition
+        $result = self::$sql->get();
+        $this->assertEquals('testvalue', $result['value']);
+    }
+
+    public function testSetOverwrite()
+    {
+        // Overwrite existing value
+        self::$sql->set('testtype', 'testkey1', 'testkey2', 'testvaluemodified', 0);
+
+        // Test that the value was actually overwriten
+        $result = self::$sql->getValue('testtype', 'testkey1', 'testkey2');
+        $this->assertEquals('testvaluemodified', $result);
+
+        $result = self::$sql->getList('testtype', 'testkey1', 'testkey2');
+        $this->assertEquals('testvaluemodified', $result[0]['value']);
+    }
+
+    public function testNonexistentKey()
+    {
+        // Test that getting some non-existing key will return null
+        $result = self::$sql->getValue('testtype_nonexistent', 'testkey1_nonexistent', 'testkey2_nonexistent');
+        $this->assertNull($result);
+        $result = self::$sql->getList('testtype_nonexistent', 'testkey1_nonexistent', 'testkey2_nonexistent');
+        $this->assertNull($result);
+        $result = self::$sql->get('testtype_nonexistent', 'testkey1_nonexistent', 'testkey2_nonexistent');
+        $this->assertNull($result);
+    }
+
+    public function testExpiration()
+    {
+        // Make sure the earlier created entry has expired now
+        sleep(1);
+
+        // Now add a second entry that never expires
+        self::$sql->set('testtype', 'testkey1_nonexpiring', 'testkey2_nonexpiring', 'testvalue_nonexpiring', null);
+
+        // Expire entries and verify that only the second one is left
+        self::$sql->removeExpired();
+        $result = self::$sql->getValue('testtype', 'testkey1', 'testkey2');
+        $this->assertNull($result);
+        $result = self::$sql->getValue('testtype', 'testkey1_nonexpiring', 'testkey2_nonexpiring');
+        $this->assertEquals('testvalue_nonexpiring', $result);
+    }
+
+    public function testRemove()
+    {
+        // Now remove the nonexpiring entry and make sure it's gone
+        self::$sql->remove('testtype', 'testkey1_nonexpiring', 'testkey2_nonexpiring');
+        $result = self::$sql->getValue('testtype', 'testkey1_nonexpiring', 'testkey2_nonexpiring');
+        $this->assertNull($result);
+    }
+}