internal SignedTokenVerificationOptions ToVerificationOptions() { var options = new SignedTokenVerificationOptions { // Google signed tokens must be signed with these certificates. CertificatesUrl = GoogleAuthConsts.JsonWebKeySetUrl, ForceCertificateRefresh = ForceGoogleCertRefresh, IssuedAtClockTolerance = IssuedAtClockTolerance, ExpiryClockTolerance = ExpirationTimeClockTolerance, CertificateCache = CertificateCache, }; options.Clock = Clock ?? options.Clock; foreach (string audience in Audience ?? Enumerable.Empty <string>()) { options.TrustedAudiences.Add(audience); } // These are the trusted issuers for Google signed tokens. foreach (string issuer in ValidJwtIssuers) { options.TrustedIssuers.Add(issuer); } return(options); }
/// <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.");
/// <summary> /// Creates a new instance of <see cref="SignedTokenVerificationOptions"/> /// by copying over all the values from <paramref name="other"/>. /// </summary> /// <param name="other">The option set to build this instance from.</param> public SignedTokenVerificationOptions(SignedTokenVerificationOptions other) { other.ThrowIfNull(nameof(other)); foreach (var audience in other.TrustedAudiences) { TrustedAudiences.Add(audience); } foreach (var issuer in other.TrustedIssuers) { TrustedIssuers.Add(issuer); } CertificatesUrl = other.CertificatesUrl; ForceCertificateRefresh = other.ForceCertificateRefresh; IssuedAtClockTolerance = other.IssuedAtClockTolerance; ExpiryClockTolerance = other.ExpiryClockTolerance; CertificateCache = other.CertificateCache; Clock = other.Clock; }
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.");
/// <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 static Task <Payload> VerifySignedTokenAsync( string signedJwt, SignedTokenVerificationOptions options = null, CancellationToken cancellationToken = default) => VerifySignedTokenAsync <Payload>(signedJwt, options, cancellationToken);