Commit a2573e3e by Michael Brachmann

webauthn rpid fix

parent da822be6
...@@ -133,6 +133,29 @@ begin ...@@ -133,6 +133,29 @@ begin
end; end;
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Extract hostname from a WebAuthn origin URL (e.g. "http://192.168.1.5:2009" → "192.168.1.5")
// This is what the browser uses as the effective domain for rpId binding.
function ExtractOriginHostname(const AOrigin: string): string;
var
s: string;
colonPos: Integer;
begin
s := Trim(AOrigin);
if s.StartsWith('https://') then Delete(s, 1, 8)
else if s.StartsWith('http://') then Delete(s, 1, 7);
// Strip port if present
colonPos := Pos(':', s);
if colonPos > 0 then
s := Copy(s, 1, colonPos - 1);
// Strip any trailing path
colonPos := Pos('/', s);
if colonPos > 0 then
s := Copy(s, 1, colonPos - 1);
Result := LowerCase(Trim(s));
end;
// ---------------------------------------------------------------------------
// BeginRegistration // BeginRegistration
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
...@@ -206,9 +229,11 @@ begin ...@@ -206,9 +229,11 @@ begin
Exit; Exit;
end; end;
var originVal: string;
try try
typeVal := cdJson.GetValue<string>('type', ''); typeVal := cdJson.GetValue<string>('type', '');
challengeVal := cdJson.GetValue<string>('challenge', ''); challengeVal := cdJson.GetValue<string>('challenge', '');
originVal := cdJson.GetValue<string>('origin', '');
finally finally
cdJson.Free; cdJson.Free;
end; end;
...@@ -228,6 +253,14 @@ begin ...@@ -228,6 +253,14 @@ begin
Exit; Exit;
end; end;
// Derive effectiveRpId from the origin the browser reported.
// Falls back to ServerConfig.rpId only if origin is absent.
var effectiveRpId := ExtractOriginHostname(originVal);
if effectiveRpId = '' then
effectiveRpId := ServerConfig.rpId;
Logger.Log(3, 'CompleteRegistration - effectiveRpId: "' + effectiveRpId + '" (origin: "' + originVal + '")');
// 3. Decode attestationObject and extract authData // 3. Decode attestationObject and extract authData
try try
attObjBytes := Base64UrlDecode(AttestationObject); attObjBytes := Base64UrlDecode(AttestationObject);
...@@ -253,13 +286,13 @@ begin ...@@ -253,13 +286,13 @@ begin
Exit; Exit;
end; end;
// 5. Verify rpIdHash // 5. Verify rpIdHash against effective rpId from clientDataJSON origin
expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(ServerConfig.rpId)); expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(effectiveRpId));
if not CompareMem(@rpIdHash[0], @expectedRpIdHash[0], 32) then if not CompareMem(@rpIdHash[0], @expectedRpIdHash[0], 32) then
begin begin
Logger.Log(2, 'CompleteRegistration - rpId hash mismatch'); Logger.Log(2, Format('CompleteRegistration - rpId hash mismatch. effectiveRpId="%s"', [effectiveRpId]));
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'rpId mismatch — check server rpId configuration.'); Result.AddPair('message', Format('rpId mismatch (server derived "%s" from origin "%s").', [effectiveRpId, originVal]));
Exit; Exit;
end; end;
...@@ -461,9 +494,11 @@ begin ...@@ -461,9 +494,11 @@ begin
if not Assigned(cdJson) then if not Assigned(cdJson) then
raise EXDataHttpUnauthorized.Create('clientDataJSON is not valid JSON.'); raise EXDataHttpUnauthorized.Create('clientDataJSON is not valid JSON.');
var loginOriginVal: string;
try try
typeVal := cdJson.GetValue<string>('type', ''); typeVal := cdJson.GetValue<string>('type', '');
challengeVal := cdJson.GetValue<string>('challenge', ''); challengeVal := cdJson.GetValue<string>('challenge', '');
loginOriginVal := cdJson.GetValue<string>('origin', '');
finally finally
cdJson.Free; cdJson.Free;
end; end;
...@@ -474,6 +509,11 @@ begin ...@@ -474,6 +509,11 @@ begin
if challengeVal <> challengeB64 then if challengeVal <> challengeB64 then
raise EXDataHttpUnauthorized.Create('Challenge mismatch.'); raise EXDataHttpUnauthorized.Create('Challenge mismatch.');
var loginEffectiveRpId := ExtractOriginHostname(loginOriginVal);
if loginEffectiveRpId = '' then
loginEffectiveRpId := ServerConfig.rpId;
Logger.Log(3, Format('AuthService.Login - effectiveRpId: "%s"', [loginEffectiveRpId]));
// 4. Load credential from DB // 4. Load credential from DB
q := TUniQuery.Create(nil); q := TUniQuery.Create(nil);
try try
...@@ -510,12 +550,13 @@ begin ...@@ -510,12 +550,13 @@ begin
if Length(authDataBytes) < 37 then if Length(authDataBytes) < 37 then
raise EXDataHttpUnauthorized.Create('authenticatorData too short.'); raise EXDataHttpUnauthorized.Create('authenticatorData too short.');
// Verify rpIdHash (first 32 bytes of authData) // Verify rpIdHash (first 32 bytes of authData) against effective rpId from origin
SetLength(rpIdHash, 32); SetLength(rpIdHash, 32);
Move(authDataBytes[0], rpIdHash[0], 32); Move(authDataBytes[0], rpIdHash[0], 32);
expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(ServerConfig.rpId)); expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(loginEffectiveRpId));
if not CompareMem(@rpIdHash[0], @expectedRpIdHash[0], 32) then if not CompareMem(@rpIdHash[0], @expectedRpIdHash[0], 32) then
raise EXDataHttpUnauthorized.Create('rpId mismatch.'); raise EXDataHttpUnauthorized.Create(
Format('rpId mismatch (server derived "%s" from origin "%s").', [loginEffectiveRpId, loginOriginVal]));
// Check user-present flag // Check user-present flag
flags := authDataBytes[32]; flags := authDataBytes[32];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment