Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 54ffd9e

Browse files
committed
merged branch sstok/fix_digest_authentication (PR #5874)
This PR was merged into the 2.0 branch. Commits ------- f2cbea3 [Security] remove escape charters from username provided by Digest DigestAuthenticationListener 80f6992 [Security] added test extra for digest authentication d66b03c fixed CS 694697d [Security] Fixed digest authentication c067586 [Security] Fixed digest authentication Discussion ---------- Fix digest authentication Bug fix: yes Feature addition: no Backwards compatibility break: no Symfony2 tests pass: yes Fixes the following tickets: Todo: - License of the code: MIT Documentation PR: - Replaces: #5485 This adds the missing fixes. My only concerns is the ```\"``` removing. ```\"``` is only needed for the HTTP transport, but keeping them would require to also store the username with the escapes as well. --------------------------------------------------------------------------- by fabpot at 2012-10-30T11:25:28Z The digest authentication mechanism is not that widespread due to its limitation. And the transport is not HTTP, I think we are talking about very few cases. --------------------------------------------------------------------------- by sstok at 2012-10-30T12:49:14Z Apache seems to remove (ignore) escape characters. ```c if (auth_line[0] == '=') { auth_line++; while (apr_isspace(auth_line[0])) { auth_line++; } vv = 0; if (auth_line[0] == '\"') { /* quoted string */ auth_line++; while (auth_line[0] != '\"' && auth_line[0] != '\0') { if (auth_line[0] == '\\' && auth_line[1] != '\0') { auth_line++; /* escaped char */ } value[vv++] = *auth_line++; } if (auth_line[0] != '\0') { auth_line++; } } else { /* token */ while (auth_line[0] != ',' && auth_line[0] != '\0' && !apr_isspace(auth_line[0])) { value[vv++] = *auth_line++; } } value[vv] = '\0'; } ``` But would this change be a BC break for people already using quotes but without a comma and thus they never hit this bug? The change it self is minimum, just calling ```str_replace('\\\\', '\\', str_replace('\\"', '"', $value))``` when getting the username. --------------------------------------------------------------------------- by fabpot at 2012-11-13T13:00:12Z @sstok Doing the same as Apache seems the best option here (just document the BC break). --------------------------------------------------------------------------- by sstok at 2012-11-15T16:05:00Z Hopefully I did this correct, but the needed escapes seem correctly removed. `\"` is changed to `"` `\\` is changed to `\` `\'` it kept as it is, as this needs no correcting. @Vincent-Simonin Can you verify please. --------------------------------------------------------------------------- by Vincent-Simonin at 2012-11-19T09:28:18Z Authentication didn't work with this configuration : ``` providers: in_memory: name: in_memory users: te"st: { password: test, roles: [ 'ROLE_USER' ] } ``` `te"st` was set in authentication form's user field. (Must we also escape `"` in configuration file ?) Tests were performed with nginx. --------------------------------------------------------------------------- by sstok at 2012-11-19T09:33:34Z Yes. YAML escapes using an duplicate quote, like SQL. ```yaml providers: in_memory: name: in_memory users: "te""st": { password: test, roles: [ 'ROLE_USER' ] } ```
2 parents bfeb6e7 + f2cbea3 commit 54ffd9e

2 files changed

Lines changed: 188 additions & 6 deletions

File tree

src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,12 @@ class DigestData
141141
public function __construct($header)
142142
{
143143
$this->header = $header;
144-
$parts = preg_split('/, /', $header);
144+
preg_match_all('/(\w+)=("((?:[^"\\\\]|\\\\.)+)"|([^\s,$]+))/', $header, $matches, PREG_SET_ORDER);
145145
$this->elements = array();
146-
foreach ($parts as $part) {
147-
list($key, $value) = explode('=', $part);
148-
$this->elements[$key] = '"' === $value[0] ? substr($value, 1, -1) : $value;
146+
foreach ($matches as $match) {
147+
if (isset($match[1]) && isset($match[3])) {
148+
$this->elements[$match[1]] = isset($match[4]) ? $match[4] : $match[3];
149+
}
149150
}
150151
}
151152

@@ -156,7 +157,7 @@ public function getResponse()
156157

157158
public function getUsername()
158159
{
159-
return $this->elements['username'];
160+
return strtr($this->elements['username'], array("\\\"" => "\"", "\\\\" => "\\"));
160161
}
161162

162163
public function validateAndDecode($entryPointKey, $expectedRealm)
@@ -188,7 +189,7 @@ public function validateAndDecode($entryPointKey, $expectedRealm)
188189
$this->nonceExpiryTime = $nonceTokens[0];
189190

190191
if (md5($this->nonceExpiryTime.':'.$entryPointKey) !== $nonceTokens[1]) {
191-
new BadCredentialsException(sprintf('Nonce token compromised "%s".', $nonceAsPlainText));
192+
throw new BadCredentialsException(sprintf('Nonce token compromised "%s".', $nonceAsPlainText));
192193
}
193194
}
194195

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Tests\Http\Firewall;
13+
14+
use Symfony\Component\Security\Http\Firewall\DigestData;
15+
16+
class DigestDataTest extends \PHPUnit_Framework_TestCase
17+
{
18+
public function testGetResponse()
19+
{
20+
$digestAuth = new DigestData(
21+
'username="user", realm="Welcome, robot!", ' .
22+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
23+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
24+
'response="b52938fc9e6d7c01be7702ece9031b42"'
25+
);
26+
27+
$this->assertEquals('b52938fc9e6d7c01be7702ece9031b42', $digestAuth->getResponse());
28+
}
29+
30+
public function testGetUsername()
31+
{
32+
$digestAuth = new DigestData(
33+
'username="user", realm="Welcome, robot!", ' .
34+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
35+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
36+
'response="b52938fc9e6d7c01be7702ece9031b42"'
37+
);
38+
39+
$this->assertEquals('user', $digestAuth->getUsername());
40+
}
41+
42+
public function testGetUsernameWithQuote()
43+
{
44+
$digestAuth = new DigestData(
45+
'username="\"user\"", realm="Welcome, robot!", ' .
46+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
47+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
48+
'response="b52938fc9e6d7c01be7702ece9031b42"'
49+
);
50+
51+
$this->assertEquals('"user"', $digestAuth->getUsername());
52+
}
53+
54+
public function testGetUsernameWithQuoteAndEscape()
55+
{
56+
$digestAuth = new DigestData(
57+
'username="\"u\\\\\"ser\"", realm="Welcome, robot!", ' .
58+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
59+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
60+
'response="b52938fc9e6d7c01be7702ece9031b42"'
61+
);
62+
63+
$this->assertEquals('"u\\"ser"', $digestAuth->getUsername());
64+
}
65+
66+
public function testGetUsernameWithSingleQuote()
67+
{
68+
$digestAuth = new DigestData(
69+
'username="\"u\'ser\"", realm="Welcome, robot!", ' .
70+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
71+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
72+
'response="b52938fc9e6d7c01be7702ece9031b42"'
73+
);
74+
75+
$this->assertEquals('"u\'ser"', $digestAuth->getUsername());
76+
}
77+
78+
public function testGetUsernameWithSingleQuoteAndEscape()
79+
{
80+
$digestAuth = new DigestData(
81+
'username="\"u\\\'ser\"", realm="Welcome, robot!", ' .
82+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
83+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
84+
'response="b52938fc9e6d7c01be7702ece9031b42"'
85+
);
86+
87+
$this->assertEquals('"u\\\'ser"', $digestAuth->getUsername());
88+
}
89+
90+
public function testGetUsernameWithEscape()
91+
{
92+
$digestAuth = new DigestData(
93+
'username="\"u\\ser\"", realm="Welcome, robot!", ' .
94+
'nonce="MTM0NzMyMTgyMy42NzkzOmRlZjM4NmIzOGNjMjE0OWJiNDU0MDAxNzJmYmM1MmZl", ' .
95+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
96+
'response="b52938fc9e6d7c01be7702ece9031b42"'
97+
);
98+
99+
$this->assertEquals('"u\\ser"', $digestAuth->getUsername());
100+
}
101+
102+
public function testValidateAndDecode()
103+
{
104+
$time = microtime(true);
105+
$key = 'ThisIsAKey';
106+
$nonce = base64_encode($time . ':' . md5($time . ':' . $key));
107+
108+
$digestAuth = new DigestData(
109+
'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' .
110+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
111+
'response="b52938fc9e6d7c01be7702ece9031b42"'
112+
);
113+
114+
try {
115+
$digestAuth->validateAndDecode($key, 'Welcome, robot!');
116+
} catch (\Exception $e) {
117+
$this->fail(sprintf('testValidateAndDecode fail with message: %s', $e->getMessage()));
118+
}
119+
}
120+
121+
public function testCalculateServerDigest()
122+
{
123+
$this->calculateServerDigest('user', 'Welcome, robot!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5');
124+
}
125+
126+
public function testCalculateServerDigestWithQuote()
127+
{
128+
$this->calculateServerDigest('\"user\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5');
129+
}
130+
131+
public function testCalculateServerDigestWithQuoteAndEscape()
132+
{
133+
$this->calculateServerDigest('\"u\\\\\"ser\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5');
134+
}
135+
136+
public function testCalculateServerDigestEscape()
137+
{
138+
$this->calculateServerDigest('\"u\\ser\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5');
139+
$this->calculateServerDigest('\"u\\ser\\\\\"', 'Welcome, \"robot\"!', 'pass,word=password', 'ThisIsAKey', '00000001', 'MDIwODkz', 'auth', 'GET', '/path/info?p1=5&p2=5');
140+
}
141+
142+
public function testIsNonceExpired()
143+
{
144+
$time = microtime(true) + 10;
145+
$key = 'ThisIsAKey';
146+
$nonce = base64_encode($time . ':' . md5($time . ':' . $key));
147+
148+
$digestAuth = new DigestData(
149+
'username="user", realm="Welcome, robot!", nonce="' . $nonce . '", ' .
150+
'uri="/path/info?p1=5&p2=5", cnonce="MDIwODkz", nc=00000001, qop="auth", ' .
151+
'response="b52938fc9e6d7c01be7702ece9031b42"'
152+
);
153+
154+
$digestAuth->validateAndDecode($key, 'Welcome, robot!');
155+
156+
$this->assertFalse($digestAuth->isNonceExpired());
157+
}
158+
159+
protected function setUp()
160+
{
161+
class_exists('Symfony\Component\Security\Http\Firewall\DigestAuthenticationListener', true);
162+
}
163+
164+
private function calculateServerDigest($username, $realm, $password, $key, $nc, $cnonce, $qop, $method, $uri)
165+
{
166+
$time = microtime(true);
167+
$nonce = base64_encode($time . ':' . md5($time . ':' . $key));
168+
169+
$response = md5(
170+
md5($username . ':' . $realm . ':' . $password) . ':' . $nonce . ':' . $nc . ':' . $cnonce . ':' . $qop . ':' . md5($method . ':' . $uri)
171+
);
172+
173+
$digest = sprintf('username="%s", realm="%s", nonce="%s", uri="%s", cnonce="%s", nc=%s, qop="%s", response="%s"',
174+
$username, $realm, $nonce, $uri, $cnonce, $nc, $qop, $response
175+
);
176+
177+
$digestAuth = new DigestData($digest);
178+
179+
$this->assertEquals($digestAuth->getResponse(), $digestAuth->calculateServerDigest($password, $method));
180+
}
181+
}

0 commit comments

Comments
 (0)