// internal for testing
        internal static async Task <Payload> ValidateInternalAsync(string jwt, ValidationSettings validationSettings)
        {
            var settings            = validationSettings.ThrowIfNull(nameof(validationSettings)).Clone();
            var verificationOptions = validationSettings.ToVerificationOptions();
            var signedToken         = SignedToken <Header, Payload> .FromSignedToken(jwt);

            // Start general validation task ...
            var generalValidationTask = SignedTokenVerification.VerifySignedTokenAsync(signedToken, verificationOptions, default);

            // ... and do Google specific validation in the meantime.

            // Google signed tokens must not exceed this length.
            // It's not clear if there's an absolute size limit for all possible tokens,
            // that's why this check is only here and not on SignedTokenVerification.
            if (jwt.Length > MaxJwtLength)
            {
                throw new InvalidJwtException($"JWT exceeds maximum allowed length of {MaxJwtLength}");
            }
            // Google signed tokens are signed with RS256.
            if (signedToken.Header.Algorithm != SupportedJwtAlgorithm)
            {
                throw new InvalidJwtException($"JWT algorithm must be '{SupportedJwtAlgorithm}'");
            }
            // Google signed tokens can contain a G Suite hosted domain claim.
            if (settings.HostedDomain != null && signedToken.Payload.HostedDomain != settings.HostedDomain)
            {
                throw new InvalidJwtException("JWT contains invalid 'hd' claim.");
            }

            // ... finally wait for the general verifications to be done.
            await generalValidationTask.ConfigureAwait(false);

            // All verification passed, return payload.
            return(signedToken.Payload);
        }
        /// <summary>
        /// Verifies that the given token is a valid, not expired, signed token.
        /// </summary>
        /// <param name="signedJwt">The token to verify.</param>
        /// <param name="options">The options to use for verification.
        /// May be null in which case default options will be used.</param>
        /// <param name="cancellationToken">The cancellation token for the operation.</param>
        /// <returns>The payload contained by the token.</returns>
        /// <exception cref="InvalidJwtException">If the token is invalid or expired.</exception>
        public async static Task <Payload> VerifySignedTokenAsync(
            string signedJwt, SignedTokenVerificationOptions options = null, CancellationToken cancellationToken = default)
        {
            var signedToken = SignedToken <Header, Payload> .FromSignedToken(signedJwt);

            await SignedTokenVerification.VerifySignedTokenAsync(signedToken, options, cancellationToken).ConfigureAwait(false);

            return(signedToken.Payload);
        }
        internal async static Task VerifySignedTokenAsync <TJswHeader, TJswPayload>(
            SignedToken <TJswHeader, TJswPayload> signedToken, SignedTokenVerificationOptions options, CancellationToken cancellationToken)
            where TJswHeader : Header
            where TJswPayload : Payload
        {
            signedToken.ThrowIfNull(nameof(signedToken));
            options = options == null ? new SignedTokenVerificationOptions() : new SignedTokenVerificationOptions(options);
            options.CertificateCache ??= s_certificateCache;

            // Start the signature validation task...
            Task signatureVerificationTask = signedToken.Header.Algorithm switch
            {
                "RS256" => VerifyRS256TokenAsync(signedToken, options, cancellationToken),
#if ES256_SUPPORTED
                "ES256" => VerifyES256TokenAsync(signedToken, options, cancellationToken),
#else
                "ES256" => throw new InvalidOperationException("ES256 signed token verification is not supported in this platform."),
#endif
                _ => throw new InvalidJwtException("Signing algorithm must be either RS256 or ES256.")
            };

            // ... let's validate everything else while we wait for signature validation...

            // The signed token issuer should be one of the trusted issuers.
            if (options.TrustedIssuers.Count > 0 && !options.TrustedIssuers.Contains(signedToken.Payload.Issuer))
            {
                var validList = string.Join(", ", options.TrustedIssuers.Select(x => $"'{x}'"));
                throw new InvalidJwtException($"JWT issuer incorrect. Must be one of: {validList}");
            }
            // All audiences on the signed token should be trusted audiences.
            if (options.TrustedAudiences.Count > 0 && signedToken.Payload.AudienceAsList.Except(options.TrustedAudiences).Any())
            {
                throw new InvalidJwtException("JWT contains untrusted 'aud' claim.");
            }
            // Issued at and expiration are present. Save them for time related validity checks.
            DateTimeOffset issuedAt  = signedToken.Payload.IssuedAt ?? throw new InvalidJwtException("JWT must contain 'iat' and 'exp' claims");
            DateTimeOffset expiresAt = signedToken.Payload.ExpiresAt ?? throw new InvalidJwtException("JWT must contain 'iat' and 'exp' claims");

            // Check that the token was issued in the past.
            var utcNow = options.Clock.UtcNow;

            if (utcNow + options.IssuedAtClockTolerance < issuedAt)
            {
                throw new InvalidJwtException("JWT is not yet valid.");
            }
            // Check that the token is not yet expired.
            if (utcNow - options.ExpiryClockTolerance > expiresAt)
            {
                throw new InvalidJwtException("JWT has expired.");
            }

            // ... and finally let's wait for signature validation to be done.
            await signatureVerificationTask.ConfigureAwait(false);
        }
        private async static Task VerifyES256TokenAsync <TJswHeader, TJswPayload>(
            SignedToken <TJswHeader, TJswPayload> signedToken, SignedTokenVerificationOptions options, CancellationToken cancellationToken)
            where TJswHeader : Header
            where TJswPayload : Payload
        {
            var certificates = await GetCertificatesAsync(
                options, GoogleAuthConsts.IapKeySetUrl, FromKeyToECDsa, cancellationToken).ConfigureAwait(false);

            foreach (ECDsa certificate in certificates)
            {
                if (certificate.VerifyHash(signedToken.Sha256Hash, signedToken.Signature))
                {
                    return;
                }
            }
            throw new InvalidJwtException("JWT invalid, unable to verify signature.");
        private async static Task VerifyRS256TokenAsync <TJswHeader, TJswPayload>(
            SignedToken <TJswHeader, TJswPayload> signedToken, SignedTokenVerificationOptions options, CancellationToken cancellationToken)
            where TJswHeader : Header
            where TJswPayload : Payload
        {
            var certificates = await GetCertificatesAsync(
                options, GoogleAuthConsts.JsonWebKeySetUrl, FromKeyToRsa, cancellationToken).ConfigureAwait(false);

            foreach (RSA certificate in certificates)
            {
#if NET45
                if (((RSACryptoServiceProvider)certificate).VerifyHash(signedToken.Sha256Hash, Sha256Oid, signedToken.Signature))
#elif NETSTANDARD1_3 || NETSTANDARD2_0 || NET461
                if (certificate.VerifyHash(signedToken.Sha256Hash, signedToken.Signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1))
#else
#error Unsupported platform
#endif
                {
                    return;
                }
            }
            throw new InvalidJwtException("JWT invalid, unable to verify signature.");