FFS what a bunch of arseholes :-(
Bluetooth is on the "client" the "device" part of "multi-device". Your
VMS code needs to be able to contact the authenticator of the public key
who will ask the client device for something like an attestation. Via
CTAP protocol.
See
https://stackoverflow.com/questions/66624283/can-i-use-phone-as-webauthn-security-key-with-windows-10-sign-in-options
for background.
This link
https://developer.apple.com/videos/play/wwdc2021/10106/ is
<30mins long, starts slow but quickly hits the spot. At 15:15 I really
started to get my jollies with the new stuff and went to bed. I'm about
to watch the rest now but guess that if your phone is bluetooth away
from your client device then all you have to do his give your
fingerprint etc on phone and it's done!!! Biometric info or private key
*NEVER* leaves the phone!!!!!!!!!!!!
VMS will *never* be a client device!
To be clear, I want to convert my code to VMS COBOL calling language
agnostic RSA, JWT, et al RTL routines that are probably there in JAVA now: -
// GET: api/<fido>
[HttpGet]
public string GetKey()
{
SigningCredentials credentials = new
SigningCredentials(_securityKey, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new JwtSecurityToken(_domain,
_domain, expires: DateTime.Now.AddSeconds(120), signingCredentials:
credentials);
return "{\"Token\": \"" + _jsth.WriteToken(token) + "\"}";
}
[HttpPost]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability",
"CA1416:Validate platform compatibility", Justification = "DSA Works on
Windows only which is fine.")]
public string VerifyAssertion([FromBody] Assertion assertion)
{
if (assertion == null || assertion.Id == null ||
assertion.AuthenticatorData == null || assertion.ClientDataJSON == null
|| assertion.Signature == null)
{
// assertion.UserHandle is null for Samsung phone
return FAIL_STATUS;
}
if (assertion.Id != tempDB.Id)
{
return FAIL_STATUS;
}
if (!ValidateClient(assertion.ClientDataJSON, "webauthn.get"))
{
return FAIL_STATUS;
}
byte[] authData =
Convert.FromBase64String(assertion.AuthenticatorData);
var creds = ValidateAuthData(authData);
if (creds == null)
{
return FAIL_STATUS;
}
creds.Id = tempDB.Id;
creds.PublicKeyJwk = tempDB.PublicKeyJwk;
byte[] hashValClientData;
try
{
hashValClientData =
_hash.ComputeHash(Encoding.Latin1.GetBytes(assertion.ClientDataJSON));
}
catch (Exception e)
{
return FAIL_STATUS;
}
PublicKey pubKey;
try
{
pubKey =
JsonConvert.DeserializeObject<PublicKey>(creds.PublicKeyJwk);
}
catch (Exception ex)
{
return FAIL_STATUS;
}
byte[] data = new byte[authData.Length +
hashValClientData.Length];
Buffer.BlockCopy(authData, 0, data, 0, authData.Length);
Buffer.BlockCopy(hashValClientData, 0, data,
authData.Length, hashValClientData.Length);
byte[] sig = Convert.FromBase64String(assertion.Signature);
if (pubKey.kty == "EC")
{
byte[] ECDsaSig = convertFromASN1(sig);
var point = new ECPoint
{
X = Convert.FromBase64String(pubKey.x),
Y = Convert.FromBase64String(pubKey.y),
};
var ecparams = new ECParameters
{
Q = point,
Curve = ECCurve.NamedCurves.nistP256
};
try
{
using (ECDsa dsa = ECDsa.Create(ecparams))
{
if (dsa.VerifyData(data, ECDsaSig,
HashAlgorithmName.SHA256))
{
Console.WriteLine("The signature is valid.");
}
else
{
Console.WriteLine("The signature is not
valid.");
return FAIL_STATUS;
}
}
}
catch (Exception e)
{
return FAIL_STATUS;
}
}
else
{
RSA rsa = RSA.Create();
rsa.ImportParameters(
new RSAParameters()
{
Modulus = Convert.FromBase64String(pubKey.n),
Exponent = Convert.FromBase64String(pubKey.e),
}
);
if (rsa.VerifyData(data, sig, HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1))
{
Console.WriteLine("The signature is valid.");
}
else
{
Console.WriteLine("The signature is not valid.");
return FAIL_STATUS;
}
}
if (creds.SignCount < tempDB.SignCount)
{
Console.WriteLine("SignCount = " + creds.SignCount + "
expected > " + tempDB.SignCount);
return FAIL_STATUS;
}
tempDB.SignCount = creds.SignCount;
return PASS_STATUS;
}