diff --git a/lib/SimpleSAML/Utils/Crypto.php b/lib/SimpleSAML/Utils/Crypto.php index c7d16921a5248322c65de9cf7238dcc87b783cb8..c3279991658a13653be9c3f8a4b222f4754c398f 100644 --- a/lib/SimpleSAML/Utils/Crypto.php +++ b/lib/SimpleSAML/Utils/Crypto.php @@ -173,6 +173,8 @@ class Crypto * 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 ''. + * @param bool $full_path Whether the filename found in the configuration contains the + * full path to the private key or not. Default to false. * * @return array|NULL Extracted private key, or NULL if no private key is present. * @throws \InvalidArgumentException If $required is not boolean or $prefix is not a string. @@ -182,9 +184,9 @@ class Crypto * @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 = '') + public static function loadPrivateKey(\SimpleSAML_Configuration $metadata, $required = false, $prefix = '', $full_path = false) { - if (!is_bool($required) || !is_string($prefix)) { + if (!is_bool($required) || !is_string($prefix) || !is_bool($full_path)) { throw new \InvalidArgumentException('Invalid input parameters.'); } @@ -198,7 +200,10 @@ class Crypto } } - $file = Config::getCertPath($file); + if (!$full_path) { + $file = Config::getCertPath($file); + } + $data = @file_get_contents($file); if ($data === false) { throw new \SimpleSAML_Error_Exception('Unable to load private key from file "'.$file.'"'); diff --git a/tests/lib/SimpleSAML/Utils/CryptoTest.php b/tests/lib/SimpleSAML/Utils/CryptoTest.php index 9563d665c4ce648119af86fa221aa95e380a9753..84eed30ee2041f6f32202b1ce3ba694c2982a78a 100644 --- a/tests/lib/SimpleSAML/Utils/CryptoTest.php +++ b/tests/lib/SimpleSAML/Utils/CryptoTest.php @@ -3,12 +3,30 @@ namespace SimpleSAML\Test\Utils; use SimpleSAML\Utils\Crypto; +use \SimpleSAML_Configuration as Configuration; + +use \org\bovigo\vfs\vfsStream; /** * Tests for SimpleSAML\Utils\Crypto. */ class CryptoTest extends \PHPUnit_Framework_TestCase { + const ROOTDIRNAME = 'testdir'; + const DEFAULTCERTDIR = 'certdir'; + + public function setUp() + { + $this->root = vfsStream::setup( + self::ROOTDIRNAME, + null, + array( + self::DEFAULTCERTDIR => array(), + ) + ); + $this->root_directory = vfsStream::url(self::ROOTDIRNAME); + $this->certdir = $this->root_directory.DIRECTORY_SEPARATOR.self::DEFAULTCERTDIR; + } /** * Test invalid input provided to the aesDecrypt() method. @@ -135,4 +153,388 @@ pfajpJ9ZzdyLIo6dVjdQtl+S1rpFCx7ziVN8tCCX4fAVCqRqZJaG/UMLvguVqayb PHP; $this->assertEquals(trim($pem), trim(Crypto::der2pem(Crypto::pem2der($pem)))); } + + /** + * @covers \SimpleSAML\Utils\Crypto::pwHash + */ + public function testGoodPwHash() + { + $pw = "password"; + $algorithm = "SHA1"; + + $res = Crypto::pwHash($pw, $algorithm); + + /* + * echo -n "password" | sha1sum | awk -F " " '{print $1}' | xxd -r -p | base64 + * W6ph5Mm5Pz8GgiULbPgzG37mj9g= + */ + $expected = "{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g="; + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::pwHash + */ + public function testGoodSaltedPwHash() + { + $pw = "password"; + $algorithm = "SSHA1"; + $salt = "salt"; + + $res = Crypto::pwHash($pw, $algorithm, $salt); + + /* + * echo -n "password""salt" | sha1sum | awk -v salt=$(echo -n "salt" | xxd -u -p) -F " " '{print $1 salt}' | xxd -r -p | base64 + * yI6cZwQadOA1e+/f+T+H3eCQQhRzYWx0 + */ + $expected = "{SSHA}yI6cZwQadOA1e+/f+T+H3eCQQhRzYWx0"; + + $this->assertEquals($expected, $res); + } + + /** + * @expectedException \SimpleSAML_Error_Exception + * + * @covers \SimpleSAML\Utils\Crypto::pwHash + */ + public function testBadHashAlgorithm() + { + $pw = "password"; + $algorithm = "wtf"; + + Crypto::pwHash($pw, $algorithm); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::pwValid + */ + public function testGoodPwValid() + { + $pw = "password"; + $algorithm = "SHA1"; + + $hash = Crypto::pwHash($pw, $algorithm); + $res = Crypto::pwValid($hash, $pw); + + $this->assertTrue($res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::pwValid + */ + public function testGoodSaltedPwValid() + { + $pw = "password"; + $algorithm = "SSHA1"; + $salt = "salt"; + + $hash = Crypto::pwHash($pw, $algorithm, $salt); + $res = Crypto::pwValid($hash, $pw); + + $this->assertTrue($res); + } + + /** + * @expectedException \SimpleSAML_Error_Exception + * + * @covers \SimpleSAML\Utils\Crypto::pwValid + */ + public function testBadHashAlgorithmValid() + { + $pw = "password"; + $algorithm = "wtf"; + $hash = "{".$algorithm."}B64STRING"; + + Crypto::pwValid($hash, $algorithm); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::secureCompare + */ + public function testSecureCompareEqual() + { + $res = Crypto::secureCompare("string", "string"); + + $this->assertTrue($res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::secureCompare + */ + public function testSecureCompareNotEqual() + { + $res = Crypto::secureCompare("string1", "string2"); + + $this->assertFalse($res); + } + + /** + * @expectedException \SimpleSAML_Error_Exception + * + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyRequiredMetadataMissing() + { + $config = new Configuration(array(), 'test'); + $required = true; + + Crypto::loadPrivateKey($config, $required); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyNotRequiredMetadataMissing() + { + $config = new Configuration(array(), 'test'); + $required = false; + + $res = Crypto::loadPrivateKey($config, $required); + + $this->assertNull($res); + } + + /** + * @expectedException \SimpleSAML_Error_Exception + * + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyMissingFile() + { + $config = new Configuration(array('privatekey' => 'nonexistant'), 'test'); + + Crypto::loadPrivateKey($config, false, '', true); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyBasic() + { + $filename = $this->certdir.DIRECTORY_SEPARATOR.'key'; + $data = 'data'; + $config = new Configuration(array('privatekey' => $filename), 'test'); + $full_path = true; + + file_put_contents($filename, $data); + + $res = Crypto::loadPrivateKey($config, false, '', $full_path); + $expected = array('PEM' => $data); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyPassword() + { + $password = 'password'; + $filename = $this->certdir.DIRECTORY_SEPARATOR.'key'; + $data = 'data'; + $config = new Configuration( + array( + 'privatekey' => $filename, + 'privatekey_pass' => $password, + ), + 'test' + ); + $full_path = true; + + file_put_contents($filename, $data); + + $res = Crypto::loadPrivateKey($config, false, '', $full_path); + $expected = array('PEM' => $data, 'password' => $password); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPrivateKey + */ + public function testLoadPrivateKeyPrefix() + { + $prefix = 'prefix'; + $password = 'password'; + $filename = $this->certdir.DIRECTORY_SEPARATOR.'key'; + $data = 'data'; + $config = new Configuration( + array( + $prefix.'privatekey' => $filename, + $prefix.'privatekey_pass' => $password, + ), + 'test' + ); + $full_path = true; + + file_put_contents($filename, $data); + + $res = Crypto::loadPrivateKey($config, false, $prefix, $full_path); + $expected = array('PEM' => $data, 'password' => $password); + + $this->assertEquals($expected, $res); + } + + /** + * @expectedException \SimpleSAML_Error_Exception + * + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyRequiredMetadataMissing() + { + $config = new Configuration(array(), 'test'); + $required = true; + + Crypto::loadPublicKey($config, $required); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyNotRequiredMetadataMissing() + { + $config = new Configuration(array(), 'test'); + $required = false; + + $res = Crypto::loadPublicKey($config, $required); + + $this->assertNull($res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyFingerprintBasicString() + { + $fingerprint = 'fingerprint'; + $config = new Configuration(array('certFingerprint' => $fingerprint), 'test'); + + $res = Crypto::loadPublicKey($config); + $expected = array('certFingerprint' => array($fingerprint)); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyFingerprintBasicArray() + { + $fingerprint1 = 'fingerprint1'; + $fingerprint2 = 'fingerprint2'; + $config = new Configuration( + array( + 'certFingerprint' => array( + $fingerprint1, + $fingerprint2 + ), + ), + 'test' + ); + + $res = Crypto::loadPublicKey($config); + $expected = array('certFingerprint' => array($fingerprint1, $fingerprint2)); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyFingerprintLowercase() + { + $fingerprint = 'FINGERPRINT'; + $config = new Configuration(array('certFingerprint' => $fingerprint), 'test'); + + $res = Crypto::loadPublicKey($config); + $expected = array('certFingerprint' => array(strtolower($fingerprint))); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyFingerprintRemoveColons() + { + $fingerprint = 'f:i:n:g:e:r:p:r:i:n:t'; + $config = new Configuration(array('certFingerprint' => $fingerprint), 'test'); + + $res = Crypto::loadPublicKey($config); + $expected = array('certFingerprint' => array(str_replace(':', '', $fingerprint))); + + $this->assertEquals($expected, $res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyNotX509Certificate() + { + $config = new Configuration( + array( + 'keys' => array( + array( + 'X509Certificate' => '', + 'type' => 'NotX509Certificate', + 'signing' => true + ), + ), + ), + 'test' + ); + + $res = Crypto::loadPublicKey($config); + + $this->assertNull($res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyNotSigning() + { + $config = new Configuration( + array( + 'keys' => array( + array( + 'X509Certificate' => '', + 'type' => 'X509Certificate', + 'signing' => false + ), + ), + ), + 'test' + ); + + $res = Crypto::loadPublicKey($config); + + $this->assertNull($res); + } + + /** + * @covers \SimpleSAML\Utils\Crypto::loadPublicKey + */ + public function testLoadPublicKeyBasic() + { + $x509certificate = 'x509certificate'; + $config = new Configuration( + array( + 'keys' => array( + array( + 'X509Certificate' => $x509certificate, + 'type' => 'X509Certificate', + 'signing' => true + ), + ), + ), + 'test' + ); + + $res = Crypto::loadPublicKey($config)['certData']; + $expected = $x509certificate; + + $this->assertEquals($expected, $res); + } }