Commit a2573e3e by Michael Brachmann

webauthn rpid fix

parent da822be6
......@@ -133,6 +133,29 @@ begin
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
// ---------------------------------------------------------------------------
......@@ -206,9 +229,11 @@ begin
Exit;
end;
var originVal: string;
try
typeVal := cdJson.GetValue<string>('type', '');
challengeVal := cdJson.GetValue<string>('challenge', '');
originVal := cdJson.GetValue<string>('origin', '');
finally
cdJson.Free;
end;
......@@ -228,6 +253,14 @@ begin
Exit;
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
try
attObjBytes := Base64UrlDecode(AttestationObject);
......@@ -253,13 +286,13 @@ begin
Exit;
end;
// 5. Verify rpIdHash
expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(ServerConfig.rpId));
// 5. Verify rpIdHash against effective rpId from clientDataJSON origin
expectedRpIdHash := SHA256Bytes(TEncoding.UTF8.GetBytes(effectiveRpId));
if not CompareMem(@rpIdHash[0], @expectedRpIdHash[0], 32) then
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('message', 'rpId mismatch — check server rpId configuration.');
Result.AddPair('message', Format('rpId mismatch (server derived "%s" from origin "%s").', [effectiveRpId, originVal]));
Exit;
end;
......@@ -461,9 +494,11 @@ begin
if not Assigned(cdJson) then
raise EXDataHttpUnauthorized.Create('clientDataJSON is not valid JSON.');
var loginOriginVal: string;
try
typeVal := cdJson.GetValue<string>('type', '');
challengeVal := cdJson.GetValue<string>('challenge', '');
typeVal := cdJson.GetValue<string>('type', '');
challengeVal := cdJson.GetValue<string>('challenge', '');
loginOriginVal := cdJson.GetValue<string>('origin', '');
finally
cdJson.Free;
end;
......@@ -474,6 +509,11 @@ begin
if challengeVal <> challengeB64 then
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
q := TUniQuery.Create(nil);
try
......@@ -510,12 +550,13 @@ begin
if Length(authDataBytes) < 37 then
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);
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
raise EXDataHttpUnauthorized.Create('rpId mismatch.');
raise EXDataHttpUnauthorized.Create(
Format('rpId mismatch (server derived "%s" from origin "%s").', [loginEffectiveRpId, loginOriginVal]));
// Check user-present flag
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