Skip to content

Commit e6d6b0c

Browse files
Properly implement RFC 5802 (SCRAM), fixes #18 (#19)
1 parent 735bdf1 commit e6d6b0c

File tree

1 file changed

+34
-5
lines changed

1 file changed

+34
-5
lines changed

src/Authentication/SCRAM.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,18 @@ private function generateResponse($challenge, $password)
165165
$serverMessageRegexp = "#^r=(?<nonce>[\x21-\x2B\x2D-\x7E/]+)"
166166
. ",s=(?<salt>(?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)?)"
167167
. ",i=(?<iteration>[0-9]*)"
168-
. "(?:,d=(?<downgradeProtection>(?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)))?"
169-
. "(,[A-Za-z]=[^,])*$#";
168+
. "(,(?<additionalAttributes>.*))?$#";
170169

171170
if (!isset($this->cnonce, $this->gs2Header) || !preg_match($serverMessageRegexp, $challenge, $matches)) {
172171
return false;
173172
}
174173

174+
$additionalAttributes = $this->parseAdditionalAttributes($matches);
175+
176+
//forbidden by RFC 5802
177+
if(isset($additionalAttributes['m']))
178+
return false;
179+
175180
$nonce = $matches['nonce'];
176181
$salt = base64_decode($matches['salt']);
177182
if (!$salt) {
@@ -186,8 +191,9 @@ private function generateResponse($challenge, $password)
186191
return false;
187192
}
188193

189-
if (!empty($matches['downgradeProtection'])) {
190-
if (!$this->downgradeProtection($matches['downgradeProtection'])) {
194+
//SSDP hash
195+
if (!empty($additionalAttributes['d'])) {
196+
if (!$this->downgradeProtection($additionalAttributes['d'])) {
191197
return false;
192198
}
193199
}
@@ -240,6 +246,23 @@ private function hi($str, $salt, $i)
240246
return $result;
241247
}
242248

249+
/**
250+
* This will parse all non-fixed-position additional SCRAM attributes (the optional ones and the m-attribute)
251+
* @param array $matches The array returned by our regex match, MUST contain an 'additionalAttributes' key
252+
* @return array
253+
*/
254+
private function parseAdditionalAttributes($matches)
255+
{
256+
$additionalAttributes=array();
257+
$tail=explode(',', $matches['additionalAttributes']);
258+
foreach($tail as $entry)
259+
{
260+
$entry=explode("=", $entry, 2);
261+
$additionalAttributes[$entry[0]] = $entry[1];
262+
}
263+
return $additionalAttributes;
264+
}
265+
243266
/**
244267
* SCRAM has also a server verification step. On a successful outcome, it will send additional data which must
245268
* absolutely be checked against this function. If this fails, the entity which we are communicating with is
@@ -251,14 +274,20 @@ private function hi($str, $salt, $i)
251274
*/
252275
public function verify($data)
253276
{
254-
$verifierRegexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)?)$#';
277+
$verifierRegexp = '#^v=((?:[A-Za-z0-9/+]{4})*(?:[A-Za-z0-9/+]{3}=|[A-Za-z0-9/+]{2}==)?)(,(?<additionalAttributes>.*))?$#';
255278

256279
$matches = array();
257280
if (!isset($this->saltedPassword, $this->authMessage) || !preg_match($verifierRegexp, $data, $matches)) {
258281
// This cannot be an outcome, you never sent the challenge's response.
259282
return false;
260283
}
261284

285+
$additionalAttributes = $this->parseAdditionalAttributes($matches);
286+
287+
//forbidden by RFC 5802
288+
if(isset($additionalAttributes['m']))
289+
return false;
290+
262291
$verifier = $matches[1];
263292
$proposedServerSignature = base64_decode($verifier);
264293
$serverKey = call_user_func($this->hmac, $this->saltedPassword, "Server Key", true);

0 commit comments

Comments
 (0)