From 9f3f55f0a1e90dc14cca7607562fc32731461e80 Mon Sep 17 00:00:00 2001
From: Jaime Perez Crespo <jaime.perez@uninett.no>
Date: Thu, 16 Apr 2015 13:10:02 +0200
Subject: [PATCH] Move loadPublicKey() and loadPrivateKey() in
 SimpleSAML_Utilities to SimpleSAML_Utils_Crypto.

Mark the old ones as deprecated and schedule them for removal in 2.0.
---
 lib/SimpleSAML/Bindings/Shib13/HTTPPost.php |   4 +-
 lib/SimpleSAML/Utilities.php                |  14 +-
 lib/SimpleSAML/Utils/Crypto.php             | 136 ++++++++++++++++++++
 modules/adfs/www/idp/metadata.php           |   6 +-
 modules/saml/lib/Message.php                |  12 +-
 modules/saml/www/idp/certs.php              |   6 +-
 modules/saml/www/sp/metadata.php            |   4 +-
 www/saml2/idp/metadata.php                  |   6 +-
 www/shib13/idp/metadata.php                 |   4 +-
 9 files changed, 165 insertions(+), 27 deletions(-)

diff --git a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
index 4a7b54ae8..7cb87a8f7 100644
--- a/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
+++ b/lib/SimpleSAML/Bindings/Shib13/HTTPPost.php
@@ -29,8 +29,8 @@ class SimpleSAML_Bindings_Shib13_HTTPPost {
 
 		SimpleSAML_Utilities::validateXMLDocument($response, 'saml11');
 
-		$privatekey = SimpleSAML_Utilities::loadPrivateKey($idpmd, TRUE);
-		$publickey = SimpleSAML_Utilities::loadPublicKey($idpmd, TRUE);
+		$privatekey = SimpleSAML_Utils_Crypto::loadPrivateKey($idpmd, TRUE);
+		$publickey = SimpleSAML_Utils_Crypto::loadPublicKey($idpmd, TRUE);
 
 		$responsedom = new DOMDocument();
 		$responsedom->loadXML(str_replace ("\r", "", $response));
diff --git a/lib/SimpleSAML/Utilities.php b/lib/SimpleSAML/Utilities.php
index fc949067e..996158360 100644
--- a/lib/SimpleSAML/Utilities.php
+++ b/lib/SimpleSAML/Utilities.php
@@ -1371,6 +1371,7 @@ class SimpleSAML_Utilities {
 	 *                        array. Defaults to ''.
 	 * @return array|NULL  Public key or certificate data, or NULL if no public key or
 	 *                     certificate was found.
+	 * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::loadPublicKey() instead.
 	 */
 	public static function loadPublicKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') {
 		assert('is_bool($required)');
@@ -1441,6 +1442,7 @@ class SimpleSAML_Utilities {
 	 * @param string $prefix  The prefix which should be used when reading from the metadata
 	 *                        array. Defaults to ''.
 	 * @return array|NULL  Extracted private key, or NULL if no private key is present.
+	 * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::loadPrivateKey() instead.
 	 */
 	public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = FALSE, $prefix = '') {
 		assert('is_bool($required)');
@@ -1585,8 +1587,8 @@ class SimpleSAML_Utilities {
 
 	/**
 	 * Input is single value or array, returns an array.
-     *
-     * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Arrays::arrayize() instead.
+	 *
+	 * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Arrays::arrayize() instead.
 	 */
 	public static function arrayize($data, $index = 0) {
 		if (is_array($data)) {
@@ -1933,8 +1935,8 @@ class SimpleSAML_Utilities {
 	 *
 	 * @param string $filename  The name of the file.
 	 * @param string $data  The data we should write to the file.
-     *
-     * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Utils_System::writeFile() instead.
+	 *
+	 * @deprecated This method will be removed in SSP 2.0. Please use SimpleSAML_Utils_System::writeFile() instead.
 	 */
 	public static function writeFile($filename, $data, $mode=0600) {
 		assert('is_string($filename)');
@@ -2244,7 +2246,7 @@ class SimpleSAML_Utilities {
 	 *
 	 * @param string $clear  Data to encrypt.
 	 * @return array  The encrypted data and IV.
-     * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::aesEncrypt() instead.
+	 * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::aesEncrypt() instead.
 	 */
 	public static function aesEncrypt($clear) {
 		assert('is_string($clear)');
@@ -2281,7 +2283,7 @@ class SimpleSAML_Utilities {
 	 * @param $data  Encrypted data.
 	 * @param $iv  IV of encrypted data.
 	 * @return string  The decrypted data.
-     * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::aesDecrypt() instead.
+	 * @deprecated This function will be removed in SSP 2.0. Please use SimpleSAML_Utils_Crypto::aesDecrypt() instead.
 	 */
 	public static function aesDecrypt($encData) {
 		assert('is_string($encData)');
diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php
index b70fb991e..565c93e0a 100644
--- a/lib/SimpleSAML/Utils/Crypto.php
+++ b/lib/SimpleSAML/Utils/Crypto.php
@@ -89,6 +89,142 @@ class SimpleSAML_Utils_Crypto
         return $iv.$data;
     }
 
+
+    /**
+     * Load a private key from metadata.
+     *
+     * This function loads a private key from a metadata array. It looks for the following elements:
+     * - 'privatekey': Name of a private key file in the cert-directory.
+     * - 'privatekey_pass': Password for the private key.
+     *
+     * It returns and array with the following elements:
+     * - 'PEM': Data for the private key, in PEM-format.
+     * - 'password': Password for the private key.
+     *
+     * @param SimpleSAML_Configuration $metadata The metadata array the private key should be loaded from.
+     * @param bool                     $required Whether the private key is required. If this is true, a
+     * missing key will cause an exception. Defaults to false.
+     * @param string                   $prefix The prefix which should be used when reading from the metadata
+     * array. Defaults to ''.
+     *
+     * @return array|NULL Extracted private key, or NULL if no private key is present.
+     * @throws SimpleSAML_Error_Exception If no private key is found in the metadata, or it was not possible to load it.
+     *
+     * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
+     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
+     */
+    public static function loadPrivateKey(SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
+    {
+        if (!is_bool($required) || !is_string($prefix)) {
+            throw new SimpleSAML_Error_Exception('Invalid input parameters.');
+        }
+
+        $file = $metadata->getString($prefix.'privatekey', null);
+        if ($file === null) {
+            // no private key found
+            if ($required) {
+                throw new SimpleSAML_Error_Exception('No private key found in metadata.');
+            } else {
+                return null;
+            }
+        }
+
+        $file = SimpleSAML_Utilities::resolveCert($file);
+        $data = @file_get_contents($file);
+        if ($data === false) {
+            throw new SimpleSAML_Error_Exception('Unable to load private key from file "'.$file.'"');
+        }
+
+        $ret = array(
+            'PEM' => $data,
+        );
+
+        if ($metadata->hasValue($prefix.'privatekey_pass')) {
+            $ret['password'] = $metadata->getString($prefix.'privatekey_pass');
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Get public key or certificate from metadata.
+     *
+     * This function implements a function to retrieve the public key or certificate from a metadata array.
+     *
+     * It will search for the following elements in the metadata:
+     * - 'certData': The certificate as a base64-encoded string.
+     * - 'certificate': A file with a certificate or public key in PEM-format.
+     * - 'certFingerprint': The fingerprint of the certificate. Can be a single fingerprint, or an array of multiple
+     * valid fingerprints.
+     *
+     * This function will return an array with these elements:
+     * - 'PEM': The public key/certificate in PEM-encoding.
+     * - 'certData': The certificate data, base64 encoded, on a single line. (Only present if this is a certificate.)
+     * - 'certFingerprint': Array of valid certificate fingerprints. (Only present if this is a certificate.)
+     *
+     * @param SimpleSAML_Configuration $metadata The metadata.
+     * @param bool                     $required Whether the private key is required. If this is TRUE, a missing key
+     *     will cause an exception. Default is FALSE.
+     * @param string                   $prefix The prefix which should be used when reading from the metadata array.
+     *     Defaults to ''.
+     *
+     * @return array|NULL Public key or certificate data, or NULL if no public key or certificate was found.
+     *
+     * @throws SimpleSAML_Error_Exception If no private key is found in the metadata, or it was not possible to load it.
+     * @author Andreas Solberg, UNINETT AS <andreas.solberg@uninett.no>
+     * @author Olav Morken, UNINETT AS <olav.morken@uninett.no>
+     * @author Lasse Birnbaum Jensen
+     */
+    public static function loadPublicKey(SimpleSAML_Configuration $metadata, $required = false, $prefix = '')
+    {
+        assert('is_bool($required)');
+        assert('is_string($prefix)');
+
+        $keys = $metadata->getPublicKeys(null, false, $prefix);
+        if ($keys !== null) {
+            foreach ($keys as $key) {
+                if ($key['type'] !== 'X509Certificate') {
+                    continue;
+                }
+                if ($key['signing'] !== true) {
+                    continue;
+                }
+                $certData = $key['X509Certificate'];
+                $pem = "-----BEGIN CERTIFICATE-----\n".
+                    chunk_split($certData, 64).
+                    "-----END CERTIFICATE-----\n";
+                $certFingerprint = strtolower(sha1(base64_decode($certData)));
+
+                return array(
+                    'certData'        => $certData,
+                    'PEM'             => $pem,
+                    'certFingerprint' => array($certFingerprint),
+                );
+            }
+            // no valid key found
+        } elseif ($metadata->hasValue($prefix.'certFingerprint')) {
+            // we only have a fingerprint available
+            $fps = $metadata->getArrayizeString($prefix.'certFingerprint');
+
+            // normalize fingerprint(s) - lowercase and no colons
+            foreach ($fps as &$fp) {
+                assert('is_string($fp)');
+                $fp = strtolower(str_replace(':', '', $fp));
+            }
+
+            // We can't build a full certificate from a fingerprint, and may as well return an array with only the
+            //fingerprint(s) immediately.
+            return array('certFingerprint' => $fps);
+        }
+
+        // no public key/certificate available
+        if ($required) {
+            throw new SimpleSAML_Error_Exception('No public key / certificate found in metadata.');
+        } else {
+            return null;
+        }
+    }
+
     /**
      * This function hashes a password with a given algorithm.
      *
diff --git a/modules/adfs/www/idp/metadata.php b/modules/adfs/www/idp/metadata.php
index 8dcd6bab8..40ddfa547 100644
--- a/modules/adfs/www/idp/metadata.php
+++ b/modules/adfs/www/idp/metadata.php
@@ -20,7 +20,7 @@ try {
 	$availableCerts = array();
 
 	$keys = array();
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, FALSE, 'new_');
 	if ($certInfo !== NULL) {
 		$availableCerts['new_idp.crt'] = $certInfo;
 		$keys[] = array(
@@ -34,7 +34,7 @@ try {
 		$hasNewCert = FALSE;
 	}
 
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE);
 	$availableCerts['idp.crt'] = $certInfo;
 	$keys[] = array(
 		'type' => 'X509Certificate',
@@ -44,7 +44,7 @@ try {
 	);
 
 	if ($idpmeta->hasValue('https.certificate')) {
-		$httpsCert = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE, 'https.');
+		$httpsCert = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE, 'https.');
 		assert('isset($httpsCert["certData"])');
 		$availableCerts['https.crt'] = $httpsCert;
 		$keys[] = array(
diff --git a/modules/saml/lib/Message.php b/modules/saml/lib/Message.php
index 0d8efe143..6e36d3623 100644
--- a/modules/saml/lib/Message.php
+++ b/modules/saml/lib/Message.php
@@ -21,11 +21,11 @@ class sspmod_saml_Message {
 		$dstPrivateKey = $dstMetadata->getString('signature.privatekey', NULL);
 
 		if ($dstPrivateKey !== NULL) {
-			$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, TRUE, 'signature.');
-			$certArray = SimpleSAML_Utilities::loadPublicKey($dstMetadata, FALSE, 'signature.');
+			$keyArray = SimpleSAML_Utils_Crypto::loadPrivateKey($dstMetadata, TRUE, 'signature.');
+			$certArray = SimpleSAML_Utils_Crypto::loadPublicKey($dstMetadata, FALSE, 'signature.');
 		} else {
-			$keyArray = SimpleSAML_Utilities::loadPrivateKey($srcMetadata, TRUE);
-			$certArray = SimpleSAML_Utilities::loadPublicKey($srcMetadata, FALSE);
+			$keyArray = SimpleSAML_Utils_Crypto::loadPrivateKey($srcMetadata, TRUE);
+			$certArray = SimpleSAML_Utils_Crypto::loadPublicKey($srcMetadata, FALSE);
 		}
 
 		$algo = $dstMetadata->getString('signature.algorithm', NULL);
@@ -281,7 +281,7 @@ class sspmod_saml_Message {
 		$keys = array();
 
 		/* Load the new private key if it exists. */
-		$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, FALSE, 'new_');
+		$keyArray = SimpleSAML_Utils_Crypto::loadPrivateKey($dstMetadata, FALSE, 'new_');
 		if ($keyArray !== NULL) {
 			assert('isset($keyArray["PEM"])');
 
@@ -294,7 +294,7 @@ class sspmod_saml_Message {
 		}
 
 		/* Find the existing private key. */
-		$keyArray = SimpleSAML_Utilities::loadPrivateKey($dstMetadata, TRUE);
+		$keyArray = SimpleSAML_Utils_Crypto::loadPrivateKey($dstMetadata, TRUE);
 		assert('isset($keyArray["PEM"])');
 
 		$key = new XMLSecurityKey(XMLSecurityKey::RSA_1_5, array('type'=>'private'));
diff --git a/modules/saml/www/idp/certs.php b/modules/saml/www/idp/certs.php
index 328cda4d2..5db029c50 100644
--- a/modules/saml/www/idp/certs.php
+++ b/modules/saml/www/idp/certs.php
@@ -17,13 +17,13 @@ $idpmeta = $metadata->getMetaDataConfig($idpentityid, 'saml20-idp-hosted');
 
 switch($_SERVER['PATH_INFO']) {
 	case '/new_idp.crt':
-		$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
+		$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, FALSE, 'new_');
 		break;
 	case '/idp.crt':
-		$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
+		$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE);
 		break;
 	case '/https.crt':
-		$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE, 'https.');
+		$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE, 'https.');
 		break;
 	default:
 		throw new SimpleSAML_Error_NotFound('Unknown certificate.');
diff --git a/modules/saml/www/sp/metadata.php b/modules/saml/www/sp/metadata.php
index 6547db14c..72d2cdd66 100644
--- a/modules/saml/www/sp/metadata.php
+++ b/modules/saml/www/sp/metadata.php
@@ -91,7 +91,7 @@ foreach ($assertionsconsumerservices as $services) {
 $metaArray20['AssertionConsumerService'] = $eps;
 
 $keys = array();
-$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig, FALSE, 'new_');
+$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($spconfig, FALSE, 'new_');
 if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 	$hasNewCert = TRUE;
 
@@ -107,7 +107,7 @@ if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 	$hasNewCert = FALSE;
 }
 
-$certInfo = SimpleSAML_Utilities::loadPublicKey($spconfig);
+$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($spconfig);
 if ($certInfo !== NULL && array_key_exists('certData', $certInfo)) {
 	$certData = $certInfo['certData'];
 
diff --git a/www/saml2/idp/metadata.php b/www/saml2/idp/metadata.php
index de515e806..2707adae6 100644
--- a/www/saml2/idp/metadata.php
+++ b/www/saml2/idp/metadata.php
@@ -22,7 +22,7 @@ try {
 	$availableCerts = array();
 
 	$keys = array();
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, FALSE, 'new_');
 	if ($certInfo !== NULL) {
 		$availableCerts['new_idp.crt'] = $certInfo;
 		$keys[] = array(
@@ -36,7 +36,7 @@ try {
 		$hasNewCert = FALSE;
 	}
 
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE);
 	$availableCerts['idp.crt'] = $certInfo;
 	$keys[] = array(
 		'type' => 'X509Certificate',
@@ -46,7 +46,7 @@ try {
 	);
 
 	if ($idpmeta->hasValue('https.certificate')) {
-		$httpsCert = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE, 'https.');
+		$httpsCert = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE, 'https.');
 		assert('isset($httpsCert["certData"])');
 		$availableCerts['https.crt'] = $httpsCert;
 		$keys[] = array(
diff --git a/www/shib13/idp/metadata.php b/www/shib13/idp/metadata.php
index e04345e20..ee35c6f6f 100644
--- a/www/shib13/idp/metadata.php
+++ b/www/shib13/idp/metadata.php
@@ -21,7 +21,7 @@ try {
 	$idpmeta = $metadata->getMetaDataConfig($idpentityid, 'shib13-idp-hosted');
 
 	$keys = array();
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, FALSE, 'new_');
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, FALSE, 'new_');
 	if ($certInfo !== NULL) {
 		$keys[] = array(
 			'type' => 'X509Certificate',
@@ -31,7 +31,7 @@ try {
 		);
 	}
 
-	$certInfo = SimpleSAML_Utilities::loadPublicKey($idpmeta, TRUE);
+	$certInfo = SimpleSAML_Utils_Crypto::loadPublicKey($idpmeta, TRUE);
 	$keys[] = array(
 		'type' => 'X509Certificate',
 		'signing' => TRUE,
-- 
GitLab