internal ECDsaCng MakeECDsa(COSE.Algorithm alg, COSE.EllipticCurve crv) { ECCurve curve; switch (alg) { case COSE.Algorithm.ES256: switch (crv) { case COSE.EllipticCurve.P256: case COSE.EllipticCurve.P256K: curve = ECCurve.NamedCurves.nistP256; break; default: throw new ArgumentOutOfRangeException(string.Format("Missing or unknown crv {0}", crv.ToString())); } break; case COSE.Algorithm.ES384: switch (crv) // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves { case COSE.EllipticCurve.P384: curve = ECCurve.NamedCurves.nistP384; break; default: throw new ArgumentOutOfRangeException(string.Format("Missing or unknown crv {0}", crv.ToString())); } break; case COSE.Algorithm.ES512: switch (crv) // https://www.iana.org/assignments/cose/cose.xhtml#elliptic-curves { case COSE.EllipticCurve.P521: curve = ECCurve.NamedCurves.nistP521; break; default: throw new ArgumentOutOfRangeException(string.Format("Missing or unknown crv {0}", crv.ToString())); } break; default: throw new ArgumentOutOfRangeException(string.Format("Missing or unknown alg {0}", alg.ToString())); } return(new ECDsaCng(curve)); }
internal async void MakeAssertionResponse(COSE.KeyType kty, COSE.Algorithm alg, COSE.EllipticCurve crv = COSE.EllipticCurve.P256) { const string rp = "fido2.azurewebsites.net"; byte[] rpId = System.Text.Encoding.UTF8.GetBytes(rp); var rpIdHash = SHA256.Create().ComputeHash(rpId); var flags = AuthenticatorFlags.AT | AuthenticatorFlags.ED | AuthenticatorFlags.UP | AuthenticatorFlags.UV; const UInt16 signCount = 0xf1d0; var aaguid = new Guid("F1D0F1D0-F1D0-F1D0-F1D0-F1D0F1D0F1D0"); var credentialID = new byte[] { 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, 0xf1, 0xd0, }; CredentialPublicKey cpk = null; ECDsaCng ecdsa = null; RSACng rsa = null; byte[] privateKeySeed, publicKey, expandedPrivateKey = null; switch (kty) { case COSE.KeyType.EC2: { ecdsa = MakeECDsa(alg, crv); var ecparams = ecdsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, crv, ecparams.Q.X, ecparams.Q.Y); break; } case COSE.KeyType.RSA: { rsa = new RSACng(); var rsaparams = rsa.ExportParameters(true); cpk = MakeCredentialPublicKey(kty, alg, rsaparams.Modulus, rsaparams.Exponent); break; } case COSE.KeyType.OKP: { MakeEdDSA(out privateKeySeed, out publicKey, out expandedPrivateKey); cpk = MakeCredentialPublicKey(kty, alg, COSE.EllipticCurve.Ed25519, publicKey); break; } throw new ArgumentOutOfRangeException(string.Format("Missing or unknown kty {0}", kty.ToString())); } var acd = new AttestedCredentialData(aaguid, credentialID, cpk); var extBytes = CBORObject.NewMap().Add("testing", true).EncodeToBytes(); var exts = new Extensions(extBytes); var ad = new AuthenticatorData(rpIdHash, flags, signCount, acd, exts); var authData = ad.ToByteArray(); var challenge = new byte[128]; var rng = RandomNumberGenerator.Create(); rng.GetBytes(challenge); var clientData = new { Type = "webauthn.get", Challenge = challenge, Origin = rp, }; var clientDataJson = System.Text.Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(clientData)); var sha = SHA256.Create(); var hashedClientDataJson = sha.ComputeHash(clientDataJson); byte[] data = new byte[authData.Length + hashedClientDataJson.Length]; Buffer.BlockCopy(authData, 0, data, 0, authData.Length); Buffer.BlockCopy(hashedClientDataJson, 0, data, authData.Length, hashedClientDataJson.Length); byte[] signature = null; switch (kty) { case COSE.KeyType.EC2: { signature = ecdsa.SignData(data, CryptoUtils.algMap[(int)alg]); break; } case COSE.KeyType.RSA: { RSASignaturePadding padding; switch (alg) // https://www.iana.org/assignments/cose/cose.xhtml#algorithms { case COSE.Algorithm.PS256: case COSE.Algorithm.PS384: case COSE.Algorithm.PS512: padding = RSASignaturePadding.Pss; break; case COSE.Algorithm.RS1: case COSE.Algorithm.RS256: case COSE.Algorithm.RS384: case COSE.Algorithm.RS512: padding = RSASignaturePadding.Pkcs1; break; default: throw new ArgumentOutOfRangeException(string.Format("Missing or unknown alg {0}", alg.ToString())); } signature = rsa.SignData(data, CryptoUtils.algMap[(int)alg], padding); break; } case COSE.KeyType.OKP: { data = CryptoUtils.GetHasher(HashAlgorithmName.SHA512).ComputeHash(data); signature = Chaos.NaCl.Ed25519.Sign(data, expandedPrivateKey); break; } throw new ArgumentOutOfRangeException(string.Format("Missing or unknown kty {0}", kty.ToString())); } if (kty == COSE.KeyType.EC2) { signature = EcDsaSigFromSig(signature, ecdsa.KeySize); } var userHandle = new byte[16]; rng.GetBytes(userHandle); var assertion = new AuthenticatorAssertionRawResponse.AssertionResponse() { AuthenticatorData = authData, Signature = signature, ClientDataJson = clientDataJson, UserHandle = userHandle, }; var lib = new Fido2(new Fido2Configuration() { ServerDomain = rp, ServerName = rp, Origin = rp, }); List <PublicKeyCredentialDescriptor> existingCredentials = new List <PublicKeyCredentialDescriptor>(); var cred = new PublicKeyCredentialDescriptor(); cred.Type = PublicKeyCredentialType.PublicKey; cred.Id = new byte[] { 0xf1, 0xd0 }; existingCredentials.Add(cred); var options = lib.GetAssertionOptions(existingCredentials, null, null); options.Challenge = challenge; var response = new AuthenticatorAssertionRawResponse() { Response = assertion, Type = PublicKeyCredentialType.PublicKey, Id = new byte[] { 0xf1, 0xd0 }, RawId = new byte[] { 0xf1, 0xd0 }, }; IsUserHandleOwnerOfCredentialIdAsync callback = (args) => { return(Task.FromResult(true)); }; var res = await lib.MakeAssertionAsync(response, options, cpk.GetBytes(), signCount - 1, callback); }