diff --git a/tests/Unit/HttpSignatures/HeaderListTest.php b/tests/Unit/HttpSignatures/HeaderListTest.php new file mode 100644 index 000000000..46d724eca --- /dev/null +++ b/tests/Unit/HttpSignatures/HeaderListTest.php @@ -0,0 +1,20 @@ +assertEquals('(request-target) date', $hl->string()); + } + + public function testFromStringRoundTripNormalized() + { + $hl = HeaderList::fromString('(request-target) Accept'); + $this->assertEquals('(request-target) accept', $hl->string()); + } +} diff --git a/tests/Unit/HttpSignatures/HmacContextTest.php b/tests/Unit/HttpSignatures/HmacContextTest.php new file mode 100644 index 000000000..d4d8cb7cc --- /dev/null +++ b/tests/Unit/HttpSignatures/HmacContextTest.php @@ -0,0 +1,190 @@ +noDigestContext = new Context([ + 'keys' => ['pda' => 'secret'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date'], + ]); + $this->withDigestContext = new Context([ + 'keys' => ['pda' => 'secret'], + 'algorithm' => 'hmac-sha256', + 'headers' => ['(request-target)', 'date', 'digest'], + ]); + } + + public function testSignerNoDigestAction() + { + $message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']); + $message = $this->noDigestContext->signer()->sign($message); + + $expectedString = implode(',', [ + 'keyId="pda"', + 'algorithm="hmac-sha256"', + 'headers="(request-target) date"', + 'signature="SFlytCGpsqb/9qYaKCQklGDvwgmrwfIERFnwt+yqPJw="', + ]); + + $this->assertEquals( + $expectedString, + $message->getHeader('Signature')[0] + ); + + $this->assertEquals( + 'Signature '.$expectedString, + $message->getHeader('Authorization')[0] + ); + } + + public function testSignerAddDigestToHeadersList() + { + $message = new Request( + 'POST', '/path/to/things?query=123', + ['date' => 'today', 'accept' => 'llamas'], + 'Thing to POST'); + $message = $this->noDigestContext->signer()->signWithDigest($message); + + $expectedString = implode(',', [ + 'keyId="pda"', + 'algorithm="hmac-sha256"', + 'headers="(request-target) date digest"', + 'signature="HH6R3OJmJbKUFqqL0tGVIIb7xi1WbbSh/HBXHUtLkUs="', ]); + $expectedDigestHeader = + 'SHA-256=rEcNhYZoBKiR29D30w1JcgArNlF8rXIXf5MnIL/4kcc='; + + $this->assertEquals( + $expectedString, + $message->getHeader('Signature')[0] + ); + + $this->assertEquals( + $expectedDigestHeader, + $message->getHeader('Digest')[0] + ); + + $this->assertEquals( + 'Signature '.$expectedString, + $message->getHeader('Authorization')[0] + ); + } + + public function testSignerReplaceDigest() + { + $message = new Request( + 'PUT', '/things/thething?query=123', + ['date' => 'today', + 'accept' => 'llamas', + 'Digest' => 'SHA-256=E/P+4y4x6EySO9qNAjCtQKxVwE1xKsNI/k+cjK+vtLU=', ], + 'Thing to PUT at /things/thething please...'); + $message = $this->noDigestContext->signer()->signWithDigest($message); + + $expectedString = implode(',', [ + 'keyId="pda"', + 'algorithm="hmac-sha256"', + 'headers="(request-target) date digest"', + 'signature="Hyatt1lSR/4XLI9Gcx8XOEKiG8LVktH7Lfr+0tmhwRU="', ]); + $expectedDigestHeader = + 'SHA-256=mulOx+77mQU1EbPET50SCGA4P/4bYxVCJA1pTwJsaMw='; + + $this->assertEquals( + $expectedString, + $message->getHeader('Signature')[0] + ); + + $this->assertEquals( + $expectedDigestHeader, + $message->getHeader('Digest')[0] + ); + + $this->assertEquals( + 'Signature '.$expectedString, + $message->getHeader('Authorization')[0] + ); + } + + public function testSignerNewDigestIsInHeaderList() + { + $message = new Request( + 'POST', '/path?query=123', + ['date' => 'today', + 'accept' => 'llamas', ], + 'Stuff that belongs in /path'); + $message = $this->withDigestContext->signer()->signWithDigest($message); + + $expectedString = implode(',', [ + 'keyId="pda"', + 'algorithm="hmac-sha256"', + 'headers="(request-target) date digest"', + 'signature="p8gQHs59X2WzQLUecfmxm1YO0OBTCNKldRZZBQsepfk="', ]); + $expectedDigestHeader = + 'SHA-256=jnSMEfBSum4Rh2k6/IVFyvLuQLmGYwMAGBS9WybyDqQ='; + + $this->assertEquals( + $expectedString, + $message->getHeader('Signature')[0] + ); + + $this->assertEquals( + $expectedDigestHeader, + $message->getHeader('Digest')[0] + ); + + $this->assertEquals( + 'Signature '.$expectedString, + $message->getHeader('Authorization')[0] + ); + } + + public function testSignerNewDigestWithoutBody() + { + $message = new Request( + 'GET', '/path?query=123', + ['date' => 'today', + 'accept' => 'llamas', ]); + $message = $this->withDigestContext->signer()->signWithDigest($message); + + $expectedString = implode(',', [ + 'keyId="pda"', + 'algorithm="hmac-sha256"', + 'headers="(request-target) date digest"', + 'signature="7iFqqryI6I9opV/Zp3eEg6PDY1tKw/3GqioOM7ACHHA="', ]); + $zeroLengthStringDigest = + 'SHA-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU='; + + $this->assertEquals( + $expectedString, + $message->getHeader('Signature')[0] + ); + + $this->assertEquals( + $zeroLengthStringDigest, + $message->getHeader('Digest')[0] + ); + + $this->assertEquals( + 'Signature '.$expectedString, + $message->getHeader('Authorization')[0] + ); + } + + public function testVerifier() + { + $message = $this->noDigestContext->signer()->sign(new Request('GET', '/path?query=123', [ + 'Signature' => 'keyId="pda",algorithm="hmac-sha1",headers="date",signature="x"', + 'Date' => 'x', + ])); + + // assert it works without errors; correctness of results tested elsewhere. + $this->assertTrue(is_bool($this->noDigestContext->verifier()->isValid($message))); + } +} diff --git a/tests/Unit/HttpSignatures/KeyStoreHmacTest.php b/tests/Unit/HttpSignatures/KeyStoreHmacTest.php new file mode 100644 index 000000000..1f47a8b1f --- /dev/null +++ b/tests/Unit/HttpSignatures/KeyStoreHmacTest.php @@ -0,0 +1,16 @@ + 'ThisIsASecretKey']); + $key = $ks->fetch('hmacsecret'); + $this->assertEquals(['hmacsecret', 'ThisIsASecretKey', 'ThisIsASecretKey', 'secret'], [ + $key->getId(), $key->getVerifyingKey(), $key->getSigningKey(), $key->getType(), ]); + } +} diff --git a/tests/Unit/HttpSignatures/KeyStoreRsaTest.php b/tests/Unit/HttpSignatures/KeyStoreRsaTest.php new file mode 100644 index 000000000..18302d44d --- /dev/null +++ b/tests/Unit/HttpSignatures/KeyStoreRsaTest.php @@ -0,0 +1,114 @@ +testRsaPrivateKeyPEM + ); + $this->testRsaPublicKeyPEM = openssl_pkey_get_details( + openssl_get_publickey(TestKeys::rsaPublicKey) + )['key']; + $this->testRsaCert = TestKeys::rsaCert; + } + + public function testParseX509inObject() + { + $keySpec = ['rsaCert' => [TestKeys::rsaCert]]; + $this->assertTrue(Key::hasX509Certificate($keySpec)); + + $ks = new KeyStore($keySpec); + $publicKey = $ks->fetch('rsaCert')->getVerifyingKey(); + $this->assertEquals('asymmetric', $ks->fetch('rsaCert')->getType()); + $this->assertEquals(TestKeys::rsaPublicKey, $publicKey); + } + + public function testParseRsaPublicKeyinObject() + { + $keySpec = ['rsaPubKey' => [TestKeys::rsaPublicKey]]; + $this->assertTrue(Key::hasPublicKey($keySpec)); + + $ks = new KeyStore($keySpec); + $publicKey = $ks->fetch('rsaPubKey')->getVerifyingKey(); + $this->assertEquals('asymmetric', $ks->fetch('rsaPubKey')->getType()); + $this->assertEquals(TestKeys::rsaPublicKey, $publicKey); + } + + public function testParsePrivateKeyinObject() + { + $keySpec = ['rsaPrivKey' => [TestKeys::rsaPrivateKey]]; + $this->assertTrue(Key::hasPrivateKey($keySpec)); + + $ks = new KeyStore($keySpec); + $publicKey = $ks->fetch('rsaPrivKey')->getSigningKey(); + $this->assertEquals('asymmetric', $ks->fetch('rsaPrivKey')->getType()); + $this->assertEquals($this->testRsaPrivateKeyPEM, $publicKey); + } + + public function testFetchRsaSigningKeySuccess() + { + $ks = new KeyStore(['rsakey' => TestKeys::rsaPrivateKey]); + $key = $ks->fetch('rsakey'); + openssl_pkey_export($key->getSigningKey(), $keyStoreSigningKey); + $this->assertEquals(['rsakey', $this->testRsaPrivateKeyPEM, null, 'asymmetric'], [ + $key->getId(), $keyStoreSigningKey, $key->getVerifyingKey(), $key->getType(), ]); + } + + public function testFetchRsaVerifyingKeyFromCertificateSuccess() + { + $ks = new KeyStore(['rsacert' => TestKeys::rsaCert]); + $key = $ks->fetch('rsacert'); + $keyStoreVerifyingKey = $key->getVerifyingKey(); + $this->assertEquals(['rsacert', null, $this->testRsaPublicKeyPEM, 'asymmetric'], [ + $key->getId(), $key->getSigningKey(), $keyStoreVerifyingKey, $key->getType(), ]); + } + + public function testFetchRsaVerifyingKeyFromPublicKeySuccess() + { + $ks = new KeyStore(['rsapubkey' => TestKeys::rsaPublicKey]); + $key = $ks->fetch('rsapubkey'); + $keyStoreVerifyingKey = $key->getVerifyingKey(); + $this->assertEquals(['rsapubkey', null, $this->testRsaPublicKeyPEM, 'asymmetric'], [ + $key->getId(), $key->getSigningKey(), $keyStoreVerifyingKey, $key->getType(), ]); + } + + public function testFetchRsaBothSuccess() + { + $ks = new KeyStore(['rsaboth' => [TestKeys::rsaCert, TestKeys::rsaPrivateKey]]); + $key = $ks->fetch('rsaboth'); + $keyStoreVerifyingKey = $key->getVerifyingKey(); + $keyStoreSigningKey = $key->getSigningKey(); + $this->assertEquals(['rsaboth', $this->testRsaPrivateKeyPEM, $this->testRsaPublicKeyPEM, 'asymmetric'], [ + $key->getId(), $keyStoreSigningKey, $keyStoreVerifyingKey, $key->getType(), ]); + } + + public function testFetchRsaBothSuccessSwitched() + { + $ks = new KeyStore(['rsabothswitch' => [TestKeys::rsaPrivateKey, TestKeys::rsaCert]]); + $key = $ks->fetch('rsabothswitch'); + $keyStoreVerifyingKey = $key->getVerifyingKey(); + $keyStoreSigningKey = $key->getSigningKey(); + $this->assertEquals(['rsabothswitch', $this->testRsaPrivateKeyPEM, $this->testRsaPublicKeyPEM, 'asymmetric'], [ + $key->getId(), $keyStoreSigningKey, $keyStoreVerifyingKey, $key->getType(), ]); + } + + /** + * @expectedException \App\Util\HttpSignatures\KeyException + */ + public function testRsaMismatch() + { + $privateKey = openssl_pkey_new([ + 'private_key_type' => 'OPENSSL_KEYTYPE_RSA', + 'private_key_bits' => 1024, ] + ); + $ks = new Key('badpki', [TestKeys::rsaCert, $privateKey]); + } +} \ No newline at end of file diff --git a/tests/Unit/HttpSignatures/KeyStoreTest.php b/tests/Unit/HttpSignatures/KeyStoreTest.php new file mode 100644 index 000000000..1e21255a1 --- /dev/null +++ b/tests/Unit/HttpSignatures/KeyStoreTest.php @@ -0,0 +1,17 @@ + 'secret']); + $key = $ks->fetch('nope'); + } +} diff --git a/tests/Unit/HttpSignatures/RsaContextTest.php b/tests/Unit/HttpSignatures/RsaContextTest.php new file mode 100644 index 000000000..78396f452 --- /dev/null +++ b/tests/Unit/HttpSignatures/RsaContextTest.php @@ -0,0 +1,84 @@ +sha1context = new Context([ + 'keys' => ['rsa1' => TestKeys::rsaPrivateKey], + 'algorithm' => 'rsa-sha1', + 'headers' => ['(request-target)', 'date'], + ]); + $this->sha256context = new Context([ + 'keys' => ['rsa1' => TestKeys::rsaPrivateKey], + 'algorithm' => 'rsa-sha256', + 'headers' => ['(request-target)', 'date'], + ]); + } + + public function testSha1Signer() + { + $message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']); + + $message = $this->sha1context->signer()->sign($message); + $expectedSha1String = implode(',', [ + 'keyId="rsa1"', + 'algorithm="rsa-sha1"', + 'headers="(request-target) date"', + 'signature="YIR3DteE3Jmz1VAnUMTgjTn3vTKfQuZl1CJhMBvGOZpnzwKeYBXA'. + 'H108FojnbSeVG/AXq9pcrA6AFK0peg0aueqxpaFlo+4L/q5XzJ+QoryY3dlSr'. + 'xwVnE5s5M19xmFm/6YkZR/KPeANCsG4SPL82Um/PCEMU0tmKd6sSx+IIzAYbX'. + 'G/VrFMDeQAdXqpU1EhgxopKEAapN8rChb49+1JfR/RxlSKiLukJJ6auurm2zM'. + 'n2D40fR1d2umA5LAO7vRt2iQwVbtwiFkVlRqkMvGftCNZByu8jJ6StI5H7Efu'. + 'ANSHAZXKXWNH8yxpBUW/QCHCZjPd0ugM0QJJIc7i8JbGlA=="', + ]); + + $this->assertEquals( + $expectedSha1String, + $message->getHeader('Signature')[0] + ); + } + + public function testSha256Signer() + { + $message = new Request('GET', '/path?query=123', ['date' => 'today', 'accept' => 'llamas']); + + $message = $this->sha256context->signer()->sign($message); + $expectedSha256String = implode(',', [ + 'keyId="rsa1"', + 'algorithm="rsa-sha256"', + 'headers="(request-target) date"', + 'signature="WGIegQCC3GEwxbkuXtq67CAqeDhkwblxAH2uoDx5kfWurhLRA5WB'. + 'FDA/aktsZAjuUoimG1w4CGxSecziER1ez44PBlHP2fCW4ArLgnQgcjkdN2cOf/g'. + 'j0OVL8s2usG4o4tud/+jjF3nxTxLl3HC+erBKsJakwXbw9kt4Cr028BToVfNXsW'. + 'oMFpv0IjcgBH2V41AVlX/mYBMMJAihBCIcpgAcGrrxmG2gkfvSn09wtTttkGHft'. + 'PIp3VpB53zbemlJS9Yw3tmmHr6cvWSXqQy/bTsEOoQJ2REfn5eiyzsJu3GiOpiI'. + 'LK67i/WH9moltJtlfV57TV72cgYtjWa6yqhtFg=="', + ]); + + $this->assertEquals( + $expectedSha256String, + $message->getHeader('Signature')[0] + ); + } + + /** + * @expectedException App\Util\HttpSignatures\AlgorithmException + */ + public function testRsaBadalgorithm() + { + $sha224context = new Context([ + 'keys' => ['rsa1' => TestKeys::rsaPrivateKey], + 'algorithm' => 'rsa-sha224', + 'headers' => ['(request-target)', 'date'], + ]); + } +} diff --git a/tests/Unit/HttpSignatures/RsaVerifierTest.php b/tests/Unit/HttpSignatures/RsaVerifierTest.php new file mode 100644 index 000000000..b6e0ded45 --- /dev/null +++ b/tests/Unit/HttpSignatures/RsaVerifierTest.php @@ -0,0 +1,156 @@ +setUpRsaVerifier(); + + $sha1SignatureHeader = + 'keyId="rsa1",algorithm="rsa-sha1",headers="(request-target) date",'. + 'signature="YIR3DteE3Jmz1VAnUMTgjTn3vTKfQuZl1CJhMBvGOZpnzwKeYBXAH10'. + '8FojnbSeVG/AXq9pcrA6AFK0peg0aueqxpaFlo+4L/q5XzJ+QoryY3dlSrxwVnE5s5'. + 'M19xmFm/6YkZR/KPeANCsG4SPL82Um/PCEMU0tmKd6sSx+IIzAYbXG/VrFMDeQAdXq'. + 'pU1EhgxopKEAapN8rChb49+1JfR/RxlSKiLukJJ6auurm2zMn2D40fR1d2umA5LAO7'. + 'vRt2iQwVbtwiFkVlRqkMvGftCNZByu8jJ6StI5H7EfuANSHAZXKXWNH8yxpBUW/QCH'. + 'CZjPd0ugM0QJJIc7i8JbGlA=="'; + + $this->sha1Message = new Request('GET', '/path?query=123', [ + 'Date' => 'today', + 'Signature' => $sha1SignatureHeader, + ]); + + $sha256SignatureHeader = + 'keyId="rsa1",algorithm="rsa-sha256",headers="(request-target) date",'. + 'signature="WGIegQCC3GEwxbkuXtq67CAqeDhkwblxAH2uoDx5kfWurhLRA5WBFDA/a'. + 'ktsZAjuUoimG1w4CGxSecziER1ez44PBlHP2fCW4ArLgnQgcjkdN2cOf/gj0OVL8s2us'. + 'G4o4tud/+jjF3nxTxLl3HC+erBKsJakwXbw9kt4Cr028BToVfNXsWoMFpv0IjcgBH2V4'. + '1AVlX/mYBMMJAihBCIcpgAcGrrxmG2gkfvSn09wtTttkGHftPIp3VpB53zbemlJS9Yw3'. + 'tmmHr6cvWSXqQy/bTsEOoQJ2REfn5eiyzsJu3GiOpiILK67i/WH9moltJtlfV57TV72c'. + 'gYtjWa6yqhtFg=="'; + + $this->sha256Message = new Request('GET', '/path?query=123', [ + 'Date' => 'today', + 'Signature' => $sha256SignatureHeader, + ]); + } + + private function setUpRsaVerifier() + { + $keyStore = new KeyStore(['rsa1' => TestKeys::rsaPublicKey]); + $this->verifier = new Verifier($keyStore); + } + + public function testVerifyValidRsaMessage() + { + $this->assertTrue($this->verifier->isValid($this->sha1Message)); + $this->assertTrue($this->verifier->isValid($this->sha256Message)); + } + + public function testVerifyValidRsaMessageAuthorizationHeader() + { + $message = $this->sha1Message->withHeader( + 'Authorization', + "Signature {$this->sha1Message->getHeader('Signature')[0]}"); + $message = $this->sha1Message->withoutHeader('Signature'); + + $this->assertTrue($this->verifier->isValid($this->sha1Message)); + + $message = $this->sha256Message->withHeader( + 'Authorization', + "Signature {$this->sha256Message->getHeader('Signature')[0]}"); + $message = $this->sha256Message->withoutHeader('Signature'); + + $this->assertTrue($this->verifier->isValid($this->sha256Message)); + } + + public function testRejectTamperedRsaRequestMethod() + { + $message = $this->sha1Message->withMethod('POST'); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withMethod('POST'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectTamperedRsaDate() + { + $message = $this->sha1Message->withHeader('Date', self::DATE_DIFFERENT); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withHeader('Date', self::DATE_DIFFERENT); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectTamperedRsaSignature() + { + $message = $this->sha1Message->withHeader( + 'Signature', + preg_replace('/signature="/', 'signature="x', $this->sha1Message->getHeader('Signature')[0]) + ); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withHeader( + 'Signature', + preg_replace('/signature="/', 'signature="x', $this->sha256Message->getHeader('Signature')[0]) + ); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectRsaMessageWithoutSignatureHeader() + { + $message = $this->sha1Message->withoutHeader('Signature'); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withoutHeader('Signature'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectRsaMessageWithGarbageSignatureHeader() + { + $message = $this->sha1Message->withHeader('Signature', 'not="a",valid="signature"'); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withHeader('Signature', 'not="a",valid="signature"'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectRsaMessageWithPartialSignatureHeader() + { + $message = $this->sha1Message->withHeader('Signature', 'keyId="aa",algorithm="bb"'); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withHeader('Signature', 'keyId="aa",algorithm="bb"'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectsRsaMessageWithUnknownKeyId() + { + $keyStore = new KeyStore(['nope' => 'secret']); + $verifier = new Verifier($keyStore); + $this->assertFalse($verifier->isValid($this->sha1Message)); + $this->assertFalse($verifier->isValid($this->sha256Message)); + } + + public function testRejectsRsaMessageMissingSignedHeaders() + { + $message = $this->sha1Message->withoutHeader('Date'); + $this->assertFalse($this->verifier->isValid($message)); + $message = $this->sha256Message->withoutHeader('Date'); + $this->assertFalse($this->verifier->isValid($message)); + } +} \ No newline at end of file diff --git a/tests/Unit/HttpSignatures/SignatureParametersParserTest.php b/tests/Unit/HttpSignatures/SignatureParametersParserTest.php new file mode 100644 index 000000000..37655092f --- /dev/null +++ b/tests/Unit/HttpSignatures/SignatureParametersParserTest.php @@ -0,0 +1,44 @@ +assertEquals( + [ + 'keyId' => 'example', + 'algorithm' => 'hmac-sha1', + 'headers' => '(request-target) date', + 'signature' => 'b64', + ], + $parser->parse() + ); + } + + /** + * @expectedException App\Util\HttpSignatures\SignatureParseException + */ + public function testParseThrowsTypedException() + { + $parser = new SignatureParametersParser('nope'); + $parser->parse(); + } + + /** + * @expectedException App\Util\HttpSignatures\SignatureParseException + */ + public function testParseExceptionForMissingComponents() + { + $parser = new SignatureParametersParser( + 'keyId="example",algorithm="hmac-sha1",headers="(request-target) date"' + ); + $parser->parse(); + } +} diff --git a/tests/Unit/HttpSignatures/SignatureParametersTest.php b/tests/Unit/HttpSignatures/SignatureParametersTest.php new file mode 100644 index 000000000..6da7252d1 --- /dev/null +++ b/tests/Unit/HttpSignatures/SignatureParametersTest.php @@ -0,0 +1,58 @@ +getMockBuilder('HttpSignatures\Signature') + ->disableOriginalConstructor() + ->getMock(); + + $signature + ->expects($this->any()) + ->method('string') + ->will($this->returnValue('thesignature')); + + $sp = new SignatureParameters($key, $algorithm, $headerList, $signature); + + $this->assertEquals( + 'keyId="pda",algorithm="hmac-sha256",headers="(request-target) date",signature="dGhlc2lnbmF0dXJl"', + $sp->string() + ); + } + + public function testRsaToString() + { + $key = new Key('pda', TestKeys::rsaPrivateKey); + $algorithm = new RsaAlgorithm('sha256'); + $headerList = new HeaderList(['(request-target)', 'date']); + + $signature = $this->getMockBuilder('HttpSignatures\Signature') + ->disableOriginalConstructor() + ->getMock(); + + $signature + ->expects($this->any()) + ->method('string') + ->will($this->returnValue('thesignature')); + + $sp = new SignatureParameters($key, $algorithm, $headerList, $signature); + + $this->assertEquals( + 'keyId="pda",algorithm="rsa-sha256",headers="(request-target) date",signature="dGhlc2lnbmF0dXJl"', + $sp->string() + ); + } +} diff --git a/tests/Unit/HttpSignatures/SigningStringTest.php b/tests/Unit/HttpSignatures/SigningStringTest.php new file mode 100644 index 000000000..87845a8a9 --- /dev/null +++ b/tests/Unit/HttpSignatures/SigningStringTest.php @@ -0,0 +1,93 @@ +message('/path')); + + $this->assertEquals( + '(request-target): get /path', + $ss->string() + ); + } + + public function testSigningStringWithOrderedQueryParameters() + { + $headerList = new HeaderList(['(request-target)', 'date']); + $ss = new SigningString($headerList, $this->message('/path?a=antelope&z=zebra')); + + $this->assertEquals( + "(request-target): get /path?a=antelope&z=zebra\ndate: Mon, 28 Jul 2014 15:39:13 -0700", + $ss->string() + ); + } + + public function testSigningStringWithUnorderedQueryParameters() + { + $headerList = new HeaderList(['(request-target)', 'date']); + $ss = new SigningString($headerList, $this->message('/path?z=zebra&a=antelope')); + + $this->assertEquals( + "(request-target): get /path?z=zebra&a=antelope\ndate: Mon, 28 Jul 2014 15:39:13 -0700", + $ss->string() + ); + } + + public function testSigningStringWithOrderedQueryParametersSymfonyRequest() + { + $headerList = new HeaderList(['(request-target)', 'date']); + $ss = new SigningString($headerList, $this->symfonyMessage('/path?a=antelope&z=zebra')); + + $this->assertEquals( + "(request-target): get /path?a=antelope&z=zebra\ndate: Mon, 28 Jul 2014 15:39:13 -0700", + $ss->string() + ); + } + + public function testSigningStringWithUnorderedQueryParametersSymfonyRequest() + { + $headerList = new HeaderList(['(request-target)', 'date']); + $ss = new SigningString($headerList, $this->symfonyMessage('/path?z=zebra&a=antelope')); + + $this->assertEquals( + "(request-target): get /path?z=zebra&a=antelope\ndate: Mon, 28 Jul 2014 15:39:13 -0700", + $ss->string() + ); + } + + /** + * @expectedException App\Util\HttpSignatures\Exception + */ + public function testSigningStringErrorForMissingHeader() + { + $headerList = new HeaderList(['nope']); + $ss = new SigningString($headerList, $this->message('/')); + $ss->string(); + } + + private function message($path) + { + return new Request('GET', $path, ['date' => 'Mon, 28 Jul 2014 15:39:13 -0700']); + } + + private function symfonyMessage($path) + { + $symfonyRequest = SymfonyRequest::create($path, 'GET'); + $symfonyRequest->headers->replace(['date' => 'Mon, 28 Jul 2014 15:39:13 -0700']); + + $psr7Factory = new DiactorosFactory(); + $psrRequest = $psr7Factory->createRequest($symfonyRequest)->withRequestTarget($symfonyRequest->getRequestUri()); + + return $psrRequest; + } +} diff --git a/tests/Unit/HttpSignatures/TestKeys.php b/tests/Unit/HttpSignatures/TestKeys.php new file mode 100644 index 000000000..cc3a28492 --- /dev/null +++ b/tests/Unit/HttpSignatures/TestKeys.php @@ -0,0 +1,64 @@ +setUpHmacVerifier(); + $this->setUpValidHmacMessage(); + } + + private function setUpHmacVerifier() + { + $keyStore = new KeyStore(['secret1' => 'secret']); + $this->verifier = new Verifier($keyStore); + } + + private function setUpValidHmacMessage() + { + $signatureHeader = sprintf( + 'keyId="%s",algorithm="%s",headers="%s",signature="%s"', + 'secret1', + 'hmac-sha256', + '(request-target) date', + 'cS2VvndvReuTLy52Ggi4j6UaDqGm9hMb4z0xJZ6adqU=' + ); + + $this->message = new Request('GET', '/path?query=123', [ + 'Date' => self::DATE, + 'Signature' => $signatureHeader, + ]); + } + + public function testVerifyValidHmacMessage() + { + $this->assertTrue($this->verifier->isValid($this->message)); + } + + public function testVerifyValidHmacMessageAuthorizationHeader() + { + $message = $this->message->withHeader('Authorization', "Signature {$this->message->getHeader('Signature')[0]}"); + $message = $message->withoutHeader('Signature'); + + $this->assertTrue($this->verifier->isValid($this->message)); + } + + public function testRejectTamperedHmacRequestMethod() + { + $message = $this->message->withMethod('POST'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectTamperedHmacDate() + { + $message = $this->message->withHeader('Date', self::DATE_DIFFERENT); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectTamperedHmacSignature() + { + $message = $this->message->withHeader( + 'Signature', + preg_replace('/signature="/', 'signature="x', $this->message->getHeader('Signature')[0]) + ); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectHmacMessageWithoutSignatureHeader() + { + $message = $this->message->withoutHeader('Signature'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectHmacMessageWithGarbageSignatureHeader() + { + $message = $this->message->withHeader('Signature', 'not="a",valid="signature"'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectHmacMessageWithPartialSignatureHeader() + { + $message = $this->message->withHeader('Signature', 'keyId="aa",algorithm="bb"'); + $this->assertFalse($this->verifier->isValid($message)); + } + + public function testRejectsHmacMessageWithUnknownKeyId() + { + $keyStore = new KeyStore(['nope' => 'secret']); + $verifier = new Verifier($keyStore); + $this->assertFalse($verifier->isValid($this->message)); + } + + public function testRejectsHmacMessageMissingSignedHeaders() + { + $message = $this->message->withoutHeader('Date'); + $this->assertFalse($this->verifier->isValid($message)); + } +}