* @author Jaime Perez, UNINETT AS */ public static function aesDecrypt($ciphertext) { if (!is_string($ciphertext)) { throw new \SimpleSAML_Error_Exception('Input parameter "$ciphertext" must be a string.'); } if (!function_exists("mcrypt_encrypt")) { throw new \SimpleSAML_Error_Exception("The mcrypt PHP module is not loaded."); } $enc = MCRYPT_RIJNDAEL_256; $mode = MCRYPT_MODE_CBC; $ivSize = mcrypt_get_iv_size($enc, $mode); $keySize = mcrypt_get_key_size($enc, $mode); $key = hash('sha256', Config::getSecretSalt(), true); $key = substr($key, 0, $keySize); $iv = substr($ciphertext, 0, $ivSize); $data = substr($ciphertext, $ivSize); $clear = mcrypt_decrypt($enc, $key, $data, $mode, $iv); $len = strlen($clear); $numpad = ord($clear[$len - 1]); $clear = substr($clear, 0, $len - $numpad); return $clear; } /** * Encrypt data using AES and the system-wide secret salt as key. * * @param string $data The data to encrypt. * * @return string The encrypted data and IV. * @throws \SimpleSAML_Error_Exception If the mcrypt module is not loaded or $data is not a string. * @author Andreas Solberg, UNINETT AS * @author Jaime Perez, UNINETT AS */ public static function aesEncrypt($data) { if (!is_string($data)) { throw new \SimpleSAML_Error_Exception('Input parameter "$data" must be a string.'); } if (!function_exists("mcrypt_encrypt")) { throw new \SimpleSAML_Error_Exception('The mcrypt PHP module is not loaded.'); } $enc = MCRYPT_RIJNDAEL_256; $mode = MCRYPT_MODE_CBC; $blockSize = mcrypt_get_block_size($enc, $mode); $ivSize = mcrypt_get_iv_size($enc, $mode); $keySize = mcrypt_get_key_size($enc, $mode); $key = hash('sha256', Config::getSecretSalt(), true); $key = substr($key, 0, $keySize); $len = strlen($data); $numpad = $blockSize - ($len % $blockSize); $data = str_pad($data, $len + $numpad, chr($numpad)); $iv = openssl_random_pseudo_bytes($ivSize); $data = mcrypt_encrypt($enc, $key, $data, $mode, $iv); 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 * @author Olav Morken, UNINETT AS */ 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 * @author Olav Morken, UNINETT AS * @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. * * @param string $password The password to hash. * @param string $algorithm The hashing algorithm, uppercase, optionally prepended with 'S' (salted). See * hash_algos() for a complete list of hashing algorithms. * @param string $salt An optional salt to use. * * @return string The hashed password. * @throws \SimpleSAML_Error_Exception If the algorithm specified is not supported, or the input parameters are not * strings. * @see hash_algos() * @author Dyonisius Visser, TERENA * @author Jaime Perez, UNINETT AS */ public static function pwHash($password, $algorithm, $salt = null) { if (!is_string($algorithm) || !is_string($password)) { throw new \SimpleSAML_Error_Exception('Invalid input parameters.'); } // hash w/o salt if (in_array(strtolower($algorithm), hash_algos())) { $alg_str = '{'.str_replace('SHA1', 'SHA', $algorithm).'}'; // LDAP compatibility $hash = hash(strtolower($algorithm), $password, true); return $alg_str.base64_encode($hash); } // hash w/ salt if (!$salt) { // no salt provided, generate one // default 8 byte salt, but 4 byte for LDAP SHA1 hashes $bytes = ($algorithm == 'SSHA1') ? 4 : 8; $salt = openssl_random_pseudo_bytes($bytes); } if ($algorithm[0] == 'S' && in_array(substr(strtolower($algorithm), 1), hash_algos())) { $alg = substr(strtolower($algorithm), 1); // 'sha256' etc $alg_str = '{'.str_replace('SSHA1', 'SSHA', $algorithm).'}'; // LDAP compatibility $hash = hash($alg, $password.$salt, true); return $alg_str.base64_encode($hash.$salt); } throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($algorithm).'\' is not supported'); } /** * This function checks if a password is valid * * @param string $hash The password as it appears in password file, optionally prepended with algorithm. * @param string $password The password to check in clear. * * @return boolean True if the hash corresponds with the given password, false otherwise. * @throws \SimpleSAML_Error_Exception If the algorithm specified is not supported, or the input parameters are not * strings. * @author Dyonisius Visser, TERENA */ public static function pwValid($hash, $password) { if (!is_string($hash) || !is_string($password)) { throw new \SimpleSAML_Error_Exception('Invalid input parameters.'); } // match algorithm string (e.g. '{SSHA256}', '{MD5}') if (preg_match('/^{(.*?)}(.*)$/', $hash, $matches)) { // LDAP compatibility $alg = preg_replace('/^(S?SHA)$/', '${1}1', $matches[1]); // hash w/o salt if (in_array(strtolower($alg), hash_algos())) { return $hash === self::pwHash($password, $alg); } // hash w/ salt if ($alg[0] === 'S' && in_array(substr(strtolower($alg), 1), hash_algos())) { $php_alg = substr(strtolower($alg), 1); // get hash length of this algorithm to learn how long the salt is $hash_length = strlen(hash($php_alg, '', true)); $salt = substr(base64_decode($matches[2]), $hash_length); return ($hash === self::pwHash($password, $alg, $salt)); } } else { return $hash === $password; } throw new \SimpleSAML_Error_Exception('Hashing algorithm \''.strtolower($alg).'\' is not supported'); } }