// 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.");