@@ -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