Files
2026-08-20 13:23:26 +08:00

123 lines
4.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Fido2NetLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudBuilder.Security.Service
{
/// <summary>
/// Re-encodes the base64url string fields on a freshly-deserialized
/// <see cref="AuthenticatorAssertionRawResponse"/> as <c>byte[]</c>.
///
/// Fido2NetLib 1.0.0-alpha has these properties typed as <c>string</c>
/// (no [JsonConverter] — that was added in v1.1.0+), but its
/// <c>MakeAssertionAsync</c> internally does
/// <c>(byte[])raw.Id</c> and
/// <c>(byte[])raw.Response.ClientDataJson</c> to look up the credential
/// and verify the signature. The hard cast throws
/// "Unable to cast object of type 'System.String' to type 'System.Byte[]'".
///
/// This helper was originally a private static method on
/// <c>UserProfileProcess</c>; it was extracted so
/// <c>FinishEpayslipBiometricAssertion</c> (the new epayslip-unlock
/// flow) can share the same Fido2NetLib 1.0.0-alpha workaround without
/// duplicating the reflection-patching logic.
///
/// Behaviour:
/// 1. Checks the runtime property type.
/// 2. If it's already <c>byte[]</c> (v1.1.0+), base64url-decodes the
/// incoming string and stores the bytes.
/// 3. If it's <c>string</c> (v1.0.0-alpha), we can't store <c>byte[]</c>
/// in a <c>string</c> field — CLR enforces type safety. Fido2NetLib's
/// internal (byte[]) cast will then throw on the next call with a
/// clear InvalidCastException we can diagnose from the stack trace.
/// </summary>
public static class WebAuthnAssertionPatcher
{
public static void PatchResponseToByteArrays(AuthenticatorAssertionRawResponse raw)
{
if (raw == null) return;
PatchStringPropertyToByteArray(raw, "Id");
PatchStringPropertyToByteArray(raw, "RawId");
if (raw.Response != null)
{
PatchStringPropertyToByteArray(raw.Response, "Id");
PatchStringPropertyToByteArray(raw.Response, "AuthenticatorData");
PatchStringPropertyToByteArray(raw.Response, "Signature");
PatchStringPropertyToByteArray(raw.Response, "ClientDataJson");
PatchStringPropertyToByteArray(raw.Response, "UserHandle");
}
}
private static void PatchStringPropertyToByteArray(object target, string propertyName)
{
if (target == null) return;
var prop = target.GetType().GetProperty(propertyName);
if (prop == null) return;
if (!prop.CanWrite) return;
var current = prop.GetValue(target);
// If Fido2NetLib 1.1.0+ shape — property is already byte[].
if (prop.PropertyType == typeof(byte[]))
{
if (current is byte[]) return; // already decoded by the converter
if (current is string s && !string.IsNullOrEmpty(s))
{
try { prop.SetValue(target, MyBase64Url.Decode(s)); }
catch { /* leave original value; Fido2NetLib will surface a clearer error */ }
}
return;
}
// If the property is `object` (1.0.0-alpha may store the raw
// deserialized value as object), we can safely put a byte[] in it.
if (prop.PropertyType == typeof(object))
{
if (current is string s && !string.IsNullOrEmpty(s))
{
try { prop.SetValue(target, MyBase64Url.Decode(s)); } catch { }
}
return;
}
// If the property is `string` (1.0.0-alpha default), we cannot
// store byte[] in a string-typed field — CLR enforces type safety
// even via reflection. Fido2NetLib's internal (byte[]) cast will
// throw on the next call. The caller will see a clear
// InvalidCastException that we can diagnose from the stack trace.
}
}
/// <summary>
/// 跨平台 Base64Url,行为等价旧Fido2NetLib.Base64Url,Windows+Linux完全兼容
/// </summary>
public static class MyBase64Url
{
public static byte[] Decode(string input)
{
if (string.IsNullOrEmpty(input))
return null;
// base64url → 标准base64
input = input.Replace('-', '+').Replace('_', '/');
switch (input.Length % 4)
{
case 2: input += "=="; break;
case 3: input += "="; break;
}
return Convert.FromBase64String(input);
}
public static string Encode(byte[] data)
{
if (data == null) return null;
return Convert.ToBase64String(data)
.Replace('+', '-')
.Replace('/', '_')
.TrimEnd('=');
}
}
}