private MetadataTOCPayload ValidatedTOCFromJwtSecurityToken(string mdsToc) { var jwtToken = new System.IdentityModel.Tokens.Jwt.JwtSecurityToken(mdsToc); tocAlg = jwtToken.Header["alg"] as string; var keys = (jwtToken.Header["x5c"] as Newtonsoft.Json.Linq.JArray) .Values <string>() .Select(x => new ECDsaSecurityKey( (ECDsaCng)(new X509Certificate2(System.Convert.FromBase64String(x)).GetECDsaPublicKey()))) .ToArray(); //var client = new System.Net.WebClient(); //var rootFile = client.DownloadData("https://mds.fidoalliance.org/Root.cer"); var rootFile = "MIICQzCCAcigAwIBAgIORqmxkzowRM99NQZJurcwCgYIKoZIzj0EAwMwUzELMAkG" + "A1UEBhMCVVMxFjAUBgNVBAoTDUZJRE8gQWxsaWFuY2UxHTAbBgNVBAsTFE1ldGFk" + "YXRhIFRPQyBTaWduaW5nMQ0wCwYDVQQDEwRSb290MB4XDTE1MDYxNzAwMDAwMFoX" + "DTQ1MDYxNzAwMDAwMFowUzELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUZJRE8gQWxs" + "aWFuY2UxHTAbBgNVBAsTFE1ldGFkYXRhIFRPQyBTaWduaW5nMQ0wCwYDVQQDEwRS" + "b290MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFEoo+6jdxg6oUuOloqPjK/nVGyY+" + "AXCFz1i5JR4OPeFJs+my143ai0p34EX4R1Xxm9xGi9n8F+RxLjLNPHtlkB3X4ims" + "rfIx7QcEImx1cMTgu5zUiwxLX1ookVhIRSoso2MwYTAOBgNVHQ8BAf8EBAMCAQYw" + "DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU0qUfC6f2YshA1Ni9udeO0VS7vEYw" + "HwYDVR0jBBgwFoAU0qUfC6f2YshA1Ni9udeO0VS7vEYwCgYIKoZIzj0EAwMDaQAw" + "ZgIxAKulGbSFkDSZusGjbNkAhAkqTkLWo3GrN5nRBNNk2Q4BlG+AvM5q9wa5WciW" + "DcMdeQIxAMOEzOFsxX9Bo0h4LOFE5y5H8bdPFYW+l5gy1tQiJv+5NUyM2IBB55XU" + "YjdBz56jSA=="; var conformanceRootFile = "MIICYjCCAeigAwIBAgIPBIdvCXPXJiuD7VW0mgRQMAoGCCqGSM49BAMDMGcxCzAJ" + "BgNVBAYTAlVTMRYwFAYDVQQKDA1GSURPIEFsbGlhbmNlMScwJQYDVQQLDB5GQUtF" + "IE1ldGFkYXRhIFRPQyBTaWduaW5nIEZBS0UxFzAVBgNVBAMMDkZBS0UgUm9vdCBG" + "QUtFMB4XDTE3MDIwMTAwMDAwMFoXDTQ1MDEzMTIzNTk1OVowZzELMAkGA1UEBhMC" + "VVMxFjAUBgNVBAoMDUZJRE8gQWxsaWFuY2UxJzAlBgNVBAsMHkZBS0UgTWV0YWRh" + "dGEgVE9DIFNpZ25pbmcgRkFLRTEXMBUGA1UEAwwORkFLRSBSb290IEZBS0UwdjAQ" + "BgcqhkjOPQIBBgUrgQQAIgNiAARcVLd6r4fnNHzs5K2zfbg//4X9/oBqmsdRVtZ9" + "iXhlgM9vFYaKviYtqmwkq0D3Lihg3qefeZgXXYi4dFgvzU7ZLBapSNM3CT8RDBe/" + "MBJqsPwaRQbIsGmmItmt/ESNQD6jWjBYMAsGA1UdDwQEAwIBBjAPBgNVHRMBAf8E" + "BTADAQH/MBsGA1UdDgQU3feayBzv4V/ToevbM18w9GoZmVkwGwYDVR0jBBTd95rI" + "HO/hX9Oh69szXzD0ahmZWTAKBggqhkjOPQQDAwNoADBlAjAfT9m8LabIuGS6tXiJ" + "mRB91SjJ49dk+sPsn+AKx1/PS3wbHEGnGxDIIcQplYDFcXICMQDi33M/oUlb7RDA" + "mapRBjJxKK+oh7hlSZv4djmZV3YV0JnF1Ed5E4I0f3C04eP0bjw="; var conformanceRoot = new X509Certificate2(System.Convert.FromBase64String(conformanceRootFile)); var root = conformance ? new X509Certificate2(Convert.FromBase64String(conformanceRootFile)) : new X509Certificate2(Convert.FromBase64String(rootFile)); var chain = new X509Chain(); chain.ChainPolicy.ExtraStore.Add(root); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = keys, }; var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); tokenHandler.ValidateToken( mdsToc, validationParameters, out var validatedToken); var payload = ((System.IdentityModel.Tokens.Jwt.JwtSecurityToken)validatedToken).Payload.SerializeToJson(); chain.ChainPolicy.ExtraStore.Add(new X509Certificate2(System.Convert.FromBase64String((jwtToken.Header["x5c"] as Newtonsoft.Json.Linq.JArray).Values <string>().Last()))); var valid = chain.Build(new X509Certificate2(System.Convert.FromBase64String((jwtToken.Header["x5c"] as Newtonsoft.Json.Linq.JArray).Values <string>().First()))); // if the root is trusted in the context we are running in, valid should be true here if (false == valid) { foreach (var element in chain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = DownloadData(cdp).Result; if (true == CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new Fido2VerificationException(string.Format("Cert {0} found in CRL {1}", element.Certificate.Subject, cdp)); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (root.Thumbprint == chain.ChainElements[chain.ChainElements.Count - 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added chain.ChainElements.Count == ((jwtToken.Header["x5c"] as Newtonsoft.Json.Linq.JArray).Count + 1) && // and that the root cert has exactly one status listed against it chain.ChainElements[chain.ChainElements.Count - 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot chain.ChainElements[chain.ChainElements.Count - 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign valid = true; for (var i = 0; i < chain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != chain.ChainElements[i].ChainElementStatus.Length) { valid = false; } } } } if (false == valid) { throw new Fido2VerificationException("Failed to validate cert chain while parsing TOC"); } return(JsonConvert.DeserializeObject <MetadataTOCPayload>(payload)); }
protected async Task <MetadataTOCPayload> DeserializeAndValidateToc(string rawTocJwt) { if (string.IsNullOrWhiteSpace(rawTocJwt)) { throw new ArgumentNullException(nameof(rawTocJwt)); } var jwtParts = rawTocJwt.Split('.'); if (jwtParts.Length != 3) { throw new ArgumentException("The JWT does not have the 3 expected components"); } var tocHeaderString = jwtParts.First(); var tocHeader = JObject.Parse(Encoding.UTF8.GetString(Base64Url.Decode(tocHeaderString))); var tocAlg = tocHeader["alg"]?.Value <string>(); if (tocAlg == null) { throw new ArgumentNullException("No alg value was present in the TOC header."); } var x5cArray = tocHeader["x5c"] as JArray; if (x5cArray == null) { throw new Exception("No x5c array was present in the TOC header."); } var keyStrings = x5cArray.Values <string>().ToList(); if (keyStrings.Count == 0) { throw new ArgumentException("No keys were present in the TOC header."); } var rootCert = GetX509Certificate(ROOT_CERT); var tocCerts = keyStrings.Select(o => GetX509Certificate(o)).ToArray(); var keys = new List <SecurityKey>(); foreach (var certString in keyStrings) { var cert = GetX509Certificate(certString); var ecdsaPublicKey = cert.GetECDsaPublicKey(); if (ecdsaPublicKey != null) { keys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); continue; } var rsaPublicKey = cert.GetRSAPublicKey(); if (rsaPublicKey != null) { keys.Add(new RsaSecurityKey(rsaPublicKey)); continue; } throw new Fido2MetadataException("Unknown certificate algorithm"); } var tocPublicKeys = keys.ToArray(); var certChain = new X509Chain(); certChain.ChainPolicy.ExtraStore.Add(rootCert); certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = tocPublicKeys, }; var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken( rawTocJwt, validationParameters, out var validatedToken); if (tocCerts.Length > 1) { certChain.ChainPolicy.ExtraStore.AddRange(tocCerts.Skip(1).ToArray()); } var certChainIsValid = certChain.Build(tocCerts.First()); // if the root is trusted in the context we are running in, valid should be true here if (!certChainIsValid) { foreach (var element in certChain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp); if (true == CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new Fido2VerificationException(string.Format("Cert {0} found in CRL {1}", element.Certificate.Subject, cdp)); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[certChain.ChainElements.Count - 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added certChain.ChainElements.Count == (keyStrings.Count + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot certChain.ChainElements[certChain.ChainElements.Count - 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign certChainIsValid = true; for (var i = 0; i < certChain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != certChain.ChainElements[i].ChainElementStatus.Length) { certChainIsValid = false; } } } } if (!certChainIsValid) { throw new Fido2VerificationException("Failed to validate cert chain while parsing TOC"); } var tocPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); var toc = Newtonsoft.Json.JsonConvert.DeserializeObject <MetadataTOCPayload>(tocPayload); toc.JwtAlg = tocAlg; return(toc); }
protected async Task <MetadataTOCPayload> DeserializeAndValidateToc(string toc) { var jwtToken = new JwtSecurityToken(toc); _tocAlg = jwtToken.Header["alg"] as string; var keys = (jwtToken.Header["x5c"] as JArray) .Values <string>() .Select(x => new ECDsaSecurityKey( (ECDsa)(new X509Certificate2(Convert.FromBase64String(x)).GetECDsaPublicKey()))) .ToArray(); var root = new X509Certificate2(Convert.FromBase64String(ROOT_CERT)); var chain = new X509Chain(); chain.ChainPolicy.ExtraStore.Add(root); chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = keys, }; var tokenHandler = new JwtSecurityTokenHandler(); tokenHandler.ValidateToken( toc, validationParameters, out var validatedToken); var payload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); chain.ChainPolicy.ExtraStore.Add(new X509Certificate2(Convert.FromBase64String((jwtToken.Header["x5c"] as JArray).Values <string>().Last()))); var valid = chain.Build(new X509Certificate2(Convert.FromBase64String((jwtToken.Header["x5c"] as JArray).Values <string>().First()))); // if the root is trusted in the context we are running in, valid should be true here if (false == valid) { foreach (var element in chain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp); if (true == CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new Fido2VerificationException(string.Format("Cert {0} found in CRL {1}", element.Certificate.Subject, cdp)); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (root.Thumbprint == chain.ChainElements[chain.ChainElements.Count - 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added chain.ChainElements.Count == ((jwtToken.Header["x5c"] as JArray).Count + 1) && // and that the root cert has exactly one status listed against it chain.ChainElements[chain.ChainElements.Count - 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot chain.ChainElements[chain.ChainElements.Count - 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign valid = true; for (var i = 0; i < chain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != chain.ChainElements[i].ChainElementStatus.Length) { valid = false; } } } } if (false == valid) { throw new Fido2VerificationException("Failed to validate cert chain while parsing TOC"); } return(JsonConvert.DeserializeObject <MetadataTOCPayload>(payload)); }
public async Task <MetadataBLOBPayload> DeserializeAndValidateBlob(string rawBLOBJwt, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(rawBLOBJwt)) { throw new ArgumentNullException(nameof(rawBLOBJwt)); } var jwtParts = rawBLOBJwt.Split('.'); if (jwtParts.Length != 3) { throw new ArgumentException("The JWT does not have the 3 expected components"); } var blobHeader = jwtParts[0]; using var jsonDoc = JsonDocument.Parse(Base64Url.Decode(blobHeader)); var tokenHeader = jsonDoc.RootElement; var blobAlg = tokenHeader.TryGetProperty("alg", out var algEl) ? algEl.GetString() ! : throw new ArgumentNullException("No alg value was present in the BLOB header."); var blobCertStrings = tokenHeader.TryGetProperty("x5c", out var x5cEl) && x5cEl.ValueKind is JsonValueKind.Array ? x5cEl.ToStringArray() : throw new ArgumentException("No x5c array was present in the BLOB header."); var rootCert = GetX509Certificate(ROOT_CERT); var blobCertificates = new X509Certificate2[blobCertStrings.Length]; var blobPublicKeys = new List <SecurityKey>(); for (int i = 0; i < blobCertStrings.Length; i++) { var cert = GetX509Certificate(blobCertStrings[i]); blobCertificates[i] = cert; if (cert.GetECDsaPublicKey() is ECDsa ecdsaPublicKey) { blobPublicKeys.Add(new ECDsaSecurityKey(ecdsaPublicKey)); } else if (cert.GetRSAPublicKey() is RSA rsa) { blobPublicKeys.Add(new RsaSecurityKey(rsa)); } } var certChain = new X509Chain(); certChain.ChainPolicy.ExtraStore.Add(rootCert); certChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck; var validationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKeys = blobPublicKeys, }; var tokenHandler = new JwtSecurityTokenHandler() { // 250k isn't enough bytes for conformance test tool // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1097 MaximumTokenSizeInBytes = rawBLOBJwt.Length }; tokenHandler.ValidateToken( rawBLOBJwt, validationParameters, out var validatedToken); if (blobCertificates.Length > 1) { certChain.ChainPolicy.ExtraStore.AddRange(blobCertificates.Skip(1).ToArray()); } var certChainIsValid = certChain.Build(blobCertificates[0]); // if the root is trusted in the context we are running in, valid should be true here if (!certChainIsValid) { foreach (var element in certChain.ChainElements) { if (element.Certificate.Issuer != element.Certificate.Subject) { var cdp = CryptoUtils.CDPFromCertificateExts(element.Certificate.Extensions); var crlFile = await DownloadDataAsync(cdp, cancellationToken); if (CryptoUtils.IsCertInCRL(crlFile, element.Certificate)) { throw new Fido2VerificationException($"Cert {element.Certificate.Subject} found in CRL {cdp}"); } } } // otherwise we have to manually validate that the root in the chain we are testing is the root we downloaded if (rootCert.Thumbprint == certChain.ChainElements[^ 1].Certificate.Thumbprint && // and that the number of elements in the chain accounts for what was in x5c plus the root we added certChain.ChainElements.Count == (blobCertStrings.Length + 1) && // and that the root cert has exactly one status listed against it certChain.ChainElements[^ 1].ChainElementStatus.Length == 1 && // and that that status is a status of exactly UntrustedRoot certChain.ChainElements[^ 1].ChainElementStatus[0].Status == X509ChainStatusFlags.UntrustedRoot) { // if we are good so far, that is a good sign certChainIsValid = true; for (var i = 0; i < certChain.ChainElements.Count - 1; i++) { // check each non-root cert to verify zero status listed against it, otherwise, invalidate chain if (0 != certChain.ChainElements[i].ChainElementStatus.Length) { certChainIsValid = false; } } } } if (!certChainIsValid) { throw new Fido2VerificationException("Failed to validate cert chain while parsing BLOB"); } var blobPayload = ((JwtSecurityToken)validatedToken).Payload.SerializeToJson(); var blob = JsonSerializer.Deserialize <MetadataBLOBPayload>(blobPayload) !; blob.JwtAlg = blobAlg; return(blob); }