Esempio n. 1
0
        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));
        }
Esempio n. 2
0
        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);
        }
Esempio n. 3
0
        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));
        }
Esempio n. 4
0
        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);
        }