/// <summary> /// Validates the 'authorizationCode' according to http://openid.net/specs/openid-connect-core-1_0.html section 3.3.2.10. /// </summary> /// <param name="jwt">a <see cref="JwtSecurityToken"/> with a 'c_hash' claim that must match <see cref="OpenIdConnectProtocolValidationContext.AuthorizationCode"/>. If <see cref="OpenIdConnectProtocolValidationContext.AuthorizationCode"/> is null, the check is not made.</param> /// <param name="validationContext">a <see cref="OpenIdConnectProtocolValidationContext"/> that contains 'c_hash' to validate.</param> /// <exception cref="ArgumentNullException">if 'jwt' is null.</exception> /// <exception cref="ArgumentNullException">if 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">if the <see cref="JwtSecurityToken"/> 'c_hash' claim does not match <see cref="OpenIdConnectProtocolValidationContext.AuthorizationCode"/> as per http://openid.net/specs/openid-connect-core-1_0.html#CodeValidation .</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">if the hash algorithm defined in <see cref="JwtHeader"/> (default is JwtAlgorithms.RSA_SHA256) was unable to be created.</exception> /// <exception cref="OpenIdConnectProtocolInvalidCHashException">if the creation of the hash algorithm return a null instance.</exception> /// <remarks>if <see cref="OpenIdConnectProtocolValidationContext.AuthorizationCode"/> is null, then the <see cref="JwtSecurityToken"/> 'c_hash' will not be validated.</remarks> protected virtual void ValidateCHash(JwtSecurityToken jwt, OpenIdConnectProtocolValidationContext validationContext) { if (jwt == null) { throw new ArgumentNullException("jwt"); } if (validationContext == null) { throw new ArgumentNullException("validationContext"); } // this handles the case the code is not expected if (validationContext.AuthorizationCode == null) { return; } if (!jwt.Payload.ContainsKey(JwtRegisteredClaimNames.CHash)) { throw new OpenIdConnectProtocolInvalidCHashException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10308, jwt)); } HashAlgorithm hashAlgorithm = null; string algorithm = jwt.Header.Alg; if (algorithm == null) { algorithm = JwtAlgorithms.RSA_SHA256; } string alg = string.Empty; if (HashAlgorithmMap.TryGetValue(algorithm, out alg)) { algorithm = alg; } try { try { hashAlgorithm = HashAlgorithm.Create(algorithm); } catch (Exception ex) { throw new OpenIdConnectProtocolInvalidCHashException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10306, algorithm, jwt), ex); } if (hashAlgorithm == null) { throw new OpenIdConnectProtocolInvalidCHashException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10307, algorithm, jwt)); } byte[] hashBytes = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(validationContext.AuthorizationCode)); string hashString = Base64UrlEncoder.Encode(hashBytes, 0, hashBytes.Length / 2); if (!StringComparer.Ordinal.Equals(jwt.Payload.CHash, hashString)) { throw new OpenIdConnectProtocolInvalidCHashException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10304, jwt.Payload.CHash, validationContext.AuthorizationCode, algorithm, jwt)); } } finally { if (hashAlgorithm != null) { hashAlgorithm.Dispose(); } } }
/// <summary> /// Validates that the <see cref="JwtSecurityToken"/> contains the nonce. /// </summary> /// <param name="jwt">a <see cref="JwtSecurityToken"/> with a 'nonce' claim that must match <see cref="OpenIdConnectProtocolValidationContext.Nonce"/>.</param> /// <param name="validationContext">a <see cref="OpenIdConnectProtocolValidationContext"/> that contains the 'nonce' to validate.</param> /// <exception cref="ArgumentNullException">if 'jwt' is null.</exception> /// <exception cref="ArgumentNullException">if 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">if a'nonce' is not found in the <see cref="JwtSecurityToken"/> and RequireNonce is true.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">if <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is null and RequireNonce is true.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">if the 'nonce' found in the <see cref="JwtSecurityToken"/> doesn't match <see cref="OpenIdConnectProtocolValidationContext.Nonce"/>.</exception> /// <exception cref="OpenIdConnectProtocolInvalidNonceException">if <see cref="RequireTimeStampInNonce"/> is true and a timestamp is not: found, well formed, negatire or expired.</exception> /// <remarks>The timestamp is only validated if <see cref="RequireTimeStampInNonce"/> is true. /// <para>If <see cref="OpenIdConnectProtocolValidationContext.Nonce"/> is not-null, then a matching 'nonce' must exist in the <see cref="JwtSecurityToken"/>.</para></remarks> protected virtual void ValidateNonce(JwtSecurityToken jwt, OpenIdConnectProtocolValidationContext validationContext) { if (jwt == null) { throw new ArgumentNullException("jwt"); } if (validationContext == null) { throw new ArgumentNullException("validationContext"); } string nonceFoundInJwt = jwt.Payload.Nonce; if (RequireNonce) { if (validationContext.Nonce == null) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10311)); } if (nonceFoundInJwt == null) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10322, jwt.ToString())); } } else if ((validationContext.Nonce != null) && (nonceFoundInJwt == null)) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10323, validationContext.Nonce, jwt.ToString())); } else if (validationContext.Nonce == null) { return; } if (!(StringComparer.Ordinal.Equals(nonceFoundInJwt, validationContext.Nonce))) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10301, validationContext.Nonce, nonceFoundInJwt, jwt.ToString())); } if (RequireTimeStampInNonce) { int endOfTimestamp = nonceFoundInJwt.IndexOf('.'); if (endOfTimestamp == -1) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10317, validationContext.Nonce)); } string timestamp = nonceFoundInJwt.Substring(0, endOfTimestamp); DateTime nonceTime; long ticks; try { ticks = Convert.ToInt64(timestamp, CultureInfo.InvariantCulture); } catch (Exception ex) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10318, timestamp, validationContext.Nonce), ex); } if (ticks <= 0) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10318, timestamp, validationContext.Nonce)); } try { nonceTime = DateTime.FromBinary(ticks); } catch (Exception ex) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10320, timestamp, System.DateTime.MinValue.Ticks.ToString(CultureInfo.InvariantCulture), System.DateTime.MaxValue.Ticks.ToString(CultureInfo.InvariantCulture)), ex); } DateTime utcNow = DateTime.UtcNow; if (nonceTime + NonceLifetime < utcNow) { throw new OpenIdConnectProtocolInvalidNonceException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10316, validationContext.Nonce, nonceTime.ToString(), utcNow.ToString(), NonceLifetime.ToString())); } } }
/// <summary> /// Validates that a <see cref="JwtSecurityToken"/> is valid as per http://openid.net/specs/openid-connect-core-1_0.html /// </summary> /// <param name="jwt">the <see cref="JwtSecurityToken"/>to validate.</param> /// <param name="validationContext">the <see cref="OpenIdConnectProtocolValidationContext"/> that contains expected values.</param> /// <exception cref="ArgumentNullException">if 'jwt' is null.</exception> /// <exception cref="ArgumentNullException">if 'validationContext' is null.</exception> /// <exception cref="OpenIdConnectProtocolException">if the <see cref="JwtSecurityToken"/> is missing any required claims as per: http://openid.net/specs/openid-connect-core-1_0.html#IDToken </exception> /// <remarks><see cref="OpenIdConnectProtocolValidationContext.Nonce"/> and <see cref="OpenIdConnectProtocolValidationContext.AuthorizationCode"/> will be validated if they are non-null.</remarks> public virtual void Validate(JwtSecurityToken jwt, OpenIdConnectProtocolValidationContext validationContext) { if (jwt == null) { throw new ArgumentNullException("jwt"); } if (validationContext == null) { throw new ArgumentNullException("validationContext"); } // required claims if (jwt.Payload.Aud.Count == 0) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10309, JwtRegisteredClaimNames.Aud.ToLowerInvariant(), jwt)); } if (!jwt.Payload.Exp.HasValue) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10309, JwtRegisteredClaimNames.Exp.ToLowerInvariant(), jwt)); } if (!jwt.Payload.Iat.HasValue) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10309, JwtRegisteredClaimNames.Iat.ToLowerInvariant(), jwt)); } if (jwt.Payload.Iss == null) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10309, JwtRegisteredClaimNames.Iss.ToLowerInvariant(), jwt)); } // sub is optional by default if (RequireSub && (string.IsNullOrWhiteSpace(jwt.Payload.Sub))) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10309, JwtRegisteredClaimNames.Sub.ToLowerInvariant(), jwt)); } // optional claims if (RequireAcr && string.IsNullOrWhiteSpace(jwt.Payload.Acr)) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10312, jwt)); } if (RequireAmr && string.IsNullOrWhiteSpace(jwt.Payload.Amr)) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10313, jwt)); } if (RequireAuthTime && string.IsNullOrWhiteSpace(jwt.Payload.AuthTime)) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10314, jwt)); } if (RequireAzp && string.IsNullOrWhiteSpace(jwt.Payload.Azp)) { throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, ErrorMessages.IDX10315, jwt)); } ValidateNonce(jwt, validationContext); ValidateCHash(jwt, validationContext); }