Esempio n. 1
0
        /// <summary>Parses and validates a JWT encoded as a JWS or JWE in compact serialized format.</summary>
        /// <param name="utf8Token">The JWT encoded as JWE or JWS.</param>
        /// <param name="policy">The validation policy.</param>
        /// <param name="jwt">The resulting <see cref="Jwt"/>.</param>
        public static bool TryParse(ReadOnlySequence <byte> utf8Token, TokenValidationPolicy policy, out Jwt jwt)
        {
            if (utf8Token.IsSingleSegment)
            {
                return(TryParse(utf8Token.First.Span, policy, out jwt));
            }

            return(TryParse(utf8Token.ToArray(), policy, out jwt));
        }
Esempio n. 2
0
        /// <summary>
        /// Builds the <see cref="TokenValidationPolicy"/>.
        /// </summary>
        /// <returns></returns>
        public TokenValidationPolicy Build()
        {
            Validate();

            var policy = new TokenValidationPolicy(
                validators: _validators.ToArray(),
                criticalHandlers: _criticalHeaderHandlers,
                maximumTokenSizeInBytes: _maximumTokenSizeInBytes,
                ignoreCriticalHeader: _ignoreCriticalHeader,
                ignoreNestedToken: _ignoreNestedToken,
                signatureValidation: _signatureValidation,
                issuer: _issuer,
                audiences: _audiences.ToArray(),
                clockSkew: _clockSkew,
                control: _control);

            return(policy);
        }
Esempio n. 3
0
        /// <summary>Parses and validates a JWT encoded as a JWS or JWE in compact serialized format.</summary>
        /// <param name="token">The JWT encoded as JWE or JWS</param>
        /// <param name="policy">The validation policy.</param>
        /// <param name="jwt">The resulting <see cref="Jwt"/>.</param>
        public static bool TryParse(string token, TokenValidationPolicy policy, out Jwt jwt)
        {
            if (token is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.token);
            }

            if (policy is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.policy);
            }

            if (token.Length == 0)
            {
                jwt = new Jwt(TokenValidationError.MalformedToken());
                return(false);
            }

            int length = Utf8.GetMaxByteCount(token.Length);

            if (length > policy.MaximumTokenSizeInBytes)
            {
                jwt = new Jwt(TokenValidationError.MalformedToken());
                return(false);
            }

            byte[]? utf8ArrayToReturnToPool = null;
            var utf8Token = length <= Constants.MaxStackallocBytes
                  ? stackalloc byte[length]
                  : (utf8ArrayToReturnToPool = ArrayPool <byte> .Shared.Rent(length));

            try
            {
                int bytesWritten = Utf8.GetBytes(token, utf8Token);
                return(TryParse(utf8Token.Slice(0, bytesWritten), policy, out jwt));
            }
            finally
            {
                if (utf8ArrayToReturnToPool != null)
                {
                    ArrayPool <byte> .Shared.Return(utf8ArrayToReturnToPool);
                }
            }
        }
Esempio n. 4
0
        /// <summary>Parses and validates a JWT encoded as a JWS or JWE in compact serialized format.</summary>
        /// <param name="utf8Token">The JWT encoded as JWE or JWS</param>
        /// <param name="policy">The validation policy.</param>
        /// <param name="jwt">The resulting <see cref="Jwt"/>.</param>
        public static bool TryParse(ReadOnlySpan <byte> utf8Token, TokenValidationPolicy policy, out Jwt jwt)
        {
            if (policy is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.policy);
            }

            TokenValidationError?error;

            if (utf8Token.IsEmpty)
            {
                error = TokenValidationError.MalformedToken();
                goto TokenError;
            }

            if (utf8Token.Length > policy.MaximumTokenSizeInBytes)
            {
                error = TokenValidationError.MalformedToken();
                goto TokenError;
            }

            Span <TokenSegment> segments    = stackalloc TokenSegment[Constants.JweSegmentCount];
            ref TokenSegment    segmentsRef = ref MemoryMarshal.GetReference(segments);
        /// <summary>Builds the <see cref="TokenValidationPolicy"/>.</summary>
        public TokenValidationPolicy Build()
        {
            Validate();

            SignatureValidationPolicy signaturePolicy;

            if (_signaturePolicies.Count == 0)
            {
                signaturePolicy = _defaultSignaturePolicy;
            }
            else if (_signaturePolicies.Count == 1)
            {
                var first = _signaturePolicies.First();
                signaturePolicy = SignatureValidationPolicy.Create(first.Key, first.Value);
            }
            else
            {
                signaturePolicy = SignatureValidationPolicy.Create(_signaturePolicies, _defaultSignaturePolicy);
            }

            var policy = new TokenValidationPolicy(
                validators: _validators.ToArray(),
                criticalHandlers: _criticalHeaderHandlers,
                maximumTokenSizeInBytes: _maximumTokenSizeInBytes,
                ignoreCriticalHeader: _ignoreCriticalHeader,
                ignoreNestedToken: _ignoreNestedToken,
                headerCacheDisabled: _headerCacheDisabled,
                signaturePolicy: signaturePolicy,
                encryptionKeyProviders: _decryptionKeysProviders,
                issuers: _issuers.ToArray(),
                audiences: _audiences.ToArray(),
                clockSkew: _clockSkew,
                control: _control);

            return(policy);
        }
Esempio n. 6
0
        internal static bool TryParseHeader(ReadOnlyMemory <byte> utf8Payload, byte[]?buffer, TokenValidationPolicy policy, [NotNullWhen(true)] out JwtHeaderDocument?header, [NotNullWhen(false)] out TokenValidationError?error)
        {
            ReadOnlySpan <byte> utf8JsonSpan = utf8Payload.Span;
            var database = new JsonMetadata(utf8Payload.Length);
            int algIdx   = -1;
            int encIdx   = -1;
            int kidIdx   = -1;
            int critIdx  = -1;

            var reader = new Utf8JsonReader(utf8JsonSpan);

            if (reader.Read())
            {
                JsonTokenType tokenType = reader.TokenType;
                if (tokenType == JsonTokenType.StartObject)
                {
                    while (reader.Read())
                    {
                        tokenType = reader.TokenType;
                        int tokenStart = (int)reader.TokenStartIndex;

                        if (tokenType == JsonTokenType.EndObject)
                        {
                            break;
                        }
                        else if (tokenType != JsonTokenType.PropertyName)
                        {
                            error = TokenValidationError.MalformedToken();
                            goto Error;
                        }

                        // Adding 1 to skip the start quote will never overflow
                        Debug.Assert(tokenStart < int.MaxValue);

                        database.Append(JsonTokenType.PropertyName, tokenStart + 1, reader.ValueSpan.Length);
                        if (reader.ValueSpan.IndexOf((byte)'\\') != -1)
                        {
                            database.SetNeedUnescaping(database.Length - JsonRow.Size);
                        }

                        ReadOnlySpan <byte> memberName = reader.ValueSpan;

                        reader.Read();
                        tokenType  = reader.TokenType;
                        tokenStart = (int)reader.TokenStartIndex;

                        // Since the input payload is contained within a Span,
                        // token start index can never be larger than int.MaxValue (i.e. utf8JsonSpan.Length).
                        Debug.Assert(reader.TokenStartIndex <= int.MaxValue);
                        if (tokenType == JsonTokenType.String)
                        {
                            if (memberName.Length == 3)
                            {
                                switch ((JwtHeaderParameters)IntegerMarshal.ReadUInt24(memberName))
                                {
                                case JwtHeaderParameters.Alg:
                                    algIdx = database.Length;
                                    break;

                                case JwtHeaderParameters.Enc:
                                    encIdx = database.Length;
                                    break;

                                case JwtHeaderParameters.Kid:
                                    kidIdx = database.Length;
                                    break;
                                }
                            }

                            // Adding 1 to skip the start quote will never overflow
                            Debug.Assert(tokenStart < int.MaxValue);
                            database.Append(JsonTokenType.String, tokenStart + 1, reader.ValueSpan.Length);
                        }
                        else if (tokenType == JsonTokenType.StartObject)
                        {
                            int count    = Utf8JsonReaderHelper.SkipObject(ref reader);
                            int index    = database.Length;
                            int tokenEnd = (int)reader.TokenStartIndex;
                            database.Append(JsonTokenType.StartObject, tokenStart, tokenEnd - tokenStart + 1);
                            database.SetNumberOfRows(index, count);
                        }
                        else if (tokenType == JsonTokenType.StartArray)
                        {
                            int count;
                            if (memberName.Length == 4 &&
                                (JwtHeaderParameters)IntegerMarshal.ReadUInt32(memberName) == JwtHeaderParameters.Crit &&
                                !policy.IgnoreCriticalHeader)
                            {
                                critIdx = database.Length;
                                if (!TryCheckCrit(ref reader, out count))
                                {
                                    error = TokenValidationError.MalformedToken("The 'crit' header parameter must be an array of string.");
                                    goto Error;
                                }
                            }
                            else
                            {
                                count = Utf8JsonReaderHelper.SkipArray(ref reader);
                            }

                            int index    = database.Length;
                            int tokenEnd = (int)reader.TokenStartIndex;
                            database.Append(JsonTokenType.StartArray, tokenStart, tokenEnd - tokenStart + 1);
                            database.SetNumberOfRows(index, count);
                        }
                        else
                        {
                            Debug.Assert(tokenType >= JsonTokenType.Number && tokenType <= JsonTokenType.Null);
                            database.Append(tokenType, tokenStart, reader.ValueSpan.Length);
                        }
                    }
                }
            }

            Debug.Assert(reader.BytesConsumed == utf8JsonSpan.Length);
            database.CompleteAllocations();

            header = new JwtHeaderDocument(new JwtDocument(utf8Payload, database, buffer), algIdx, encIdx, kidIdx, critIdx);
#if NET5_0_OR_GREATER
            Unsafe.SkipInit(out error);
#else
            error = default;
#endif
            return(true);

Error:
#if NET5_0_OR_GREATER
            Unsafe.SkipInit(out header);
#else
            header = default;
#endif
            return(false);
        }
Esempio n. 7
0
        internal static bool TryParsePayload(ReadOnlyMemory <byte> utf8Payload, byte[]?buffer, TokenValidationPolicy policy, [NotNullWhen(true)] out JwtPayloadDocument?payload, [NotNullWhen(false)] out TokenValidationError?error)
        {
            ReadOnlySpan <byte> utf8JsonSpan = utf8Payload.Span;
            var  database = new JsonMetadata(utf8Payload.Length);
            byte control  = policy.Control;
            int  issIdx   = -1;

            var reader = new Utf8JsonReader(utf8JsonSpan);

            if (reader.Read())
            {
                JsonTokenType tokenType = reader.TokenType;
                if (tokenType == JsonTokenType.StartObject)
                {
                    while (reader.Read())
                    {
                        tokenType = reader.TokenType;
                        int tokenStart = (int)reader.TokenStartIndex;

                        if (tokenType == JsonTokenType.EndObject)
                        {
                            break;
                        }
                        else if (tokenType != JsonTokenType.PropertyName)
                        {
                            error = TokenValidationError.MalformedToken();
                            goto Error;
                        }

                        Debug.Assert(tokenStart < int.MaxValue);
                        database.Append(JsonTokenType.PropertyName, tokenStart + 1, reader.ValueSpan.Length);
                        ReadOnlySpan <byte> memberName = reader.ValueSpan;
                        if (memberName.IndexOf((byte)'\\') != -1)
                        {
                            database.SetNeedUnescaping(database.Length - JsonRow.Size);
                        }

                        reader.Read();
                        tokenType  = reader.TokenType;
                        tokenStart = (int)reader.TokenStartIndex;

                        Debug.Assert(reader.TokenStartIndex <= int.MaxValue);
                        if (tokenType == JsonTokenType.String)
                        {
                            if (memberName.Length == 3)
                            {
                                switch ((JwtClaims)IntegerMarshal.ReadUInt24(memberName))
                                {
                                case JwtClaims.Aud:
                                    CheckStringAudience(ref reader, ref control, policy);
                                    break;

                                case JwtClaims.Iss:
                                    issIdx = database.Length;
                                    CheckIssuer(ref reader, ref control, policy);
                                    break;
                                }
                            }

                            Debug.Assert(tokenStart < int.MaxValue);
                            database.Append(JsonTokenType.String, tokenStart + 1, reader.ValueSpan.Length);
                        }
                        else if (tokenType == JsonTokenType.Number)
                        {
                            if (memberName.Length == 3)
                            {
                                switch ((JwtClaims)IntegerMarshal.ReadUInt24(memberName))
                                {
                                case JwtClaims.Exp:
                                    if (!TryCheckExpirationTime(ref reader, ref control, policy))
                                    {
                                        error = TokenValidationError.MalformedToken("The claim 'exp' must be an integral number.");
                                        goto Error;
                                    }
                                    break;

                                case JwtClaims.Nbf:
                                    if (!TryCheckNotBefore(ref reader, ref control, policy))
                                    {
                                        error = TokenValidationError.MalformedToken("The claim 'nbf' must be an integral number.");
                                        goto Error;
                                    }
                                    break;
                                }
                            }

                            database.Append(JsonTokenType.Number, tokenStart, reader.ValueSpan.Length);
                        }
                        else if (tokenType == JsonTokenType.StartObject)
                        {
                            int itemCount = Utf8JsonReaderHelper.SkipObject(ref reader);
                            int tokenEnd  = (int)reader.TokenStartIndex;
                            int index     = database.Length;
                            database.Append(JsonTokenType.StartObject, tokenStart, tokenEnd - tokenStart + 1);
                            database.SetNumberOfRows(index, itemCount);
                        }
                        else if (tokenType == JsonTokenType.StartArray)
                        {
                            int itemCount;
                            if (memberName.Length == 3 && (JwtClaims)IntegerMarshal.ReadUInt24(memberName) == JwtClaims.Aud)
                            {
                                itemCount = CheckArrayAudience(ref reader, ref control, policy);
                            }
                            else
                            {
                                itemCount = Utf8JsonReaderHelper.SkipArray(ref reader);
                            }

                            int index    = database.Length;
                            int tokenEnd = (int)reader.TokenStartIndex;
                            database.Append(JsonTokenType.StartArray, tokenStart, tokenEnd - tokenStart + 1);
                            database.SetNumberOfRows(index, itemCount);
                        }
                        else
                        {
                            Debug.Assert(tokenType >= JsonTokenType.True && tokenType <= JsonTokenType.Null);
                            database.Append(tokenType, tokenStart, reader.ValueSpan.Length);
                        }
                    }
                }
            }

            Debug.Assert(reader.BytesConsumed == utf8JsonSpan.Length);
            database.CompleteAllocations();

            payload = new JwtPayloadDocument(new JwtDocument(utf8Payload, database, buffer), control, issIdx);
#if NET5_0_OR_GREATER
            Unsafe.SkipInit(out error);
#else
            error = default;
#endif
            return(true);

Error:
#if NET5_0_OR_GREATER
            Unsafe.SkipInit(out payload);
#else
            payload = default;
#endif
            return(false);
        }
Esempio n. 8
0
        private static int CheckArrayAudience(ref Utf8JsonReader reader, ref byte validationControl, TokenValidationPolicy policy)
        {
            int count = 0;

            if (policy.RequireAudience)
            {
                validationControl &= unchecked ((byte)~TokenValidationPolicy.MissingAudienceMask);
                while (reader.Read() && reader.TokenType == JsonTokenType.String)
                {
                    count++;
                    var requiredAudiences = policy.RequiredAudiencesBinary;
                    for (int i = 0; i < requiredAudiences.Length; i++)
                    {
                        if (reader.ValueTextEquals(requiredAudiences[i]))
                        {
                            validationControl &= unchecked ((byte)~TokenValidationPolicy.AudienceMask);
                            while (reader.Read() && reader.TokenType == JsonTokenType.String)
                            {
                                // Just read...
                                count++;
                            }

                            goto Found;
                        }
                    }
                }
            }
            else
            {
                while (reader.Read() && reader.TokenType == JsonTokenType.String)
                {
                    // Just read...
                    count++;
                }
            }

Found:
            if (reader.TokenType != JsonTokenType.EndArray)
            {
                ThrowHelper.ThrowFormatException_MalformedJson("The 'aud' claim must be an array of string or a string.");
            }

            return(count);
        }
Esempio n. 9
0
 private static void CheckStringAudience(ref Utf8JsonReader reader, ref byte validationControl, TokenValidationPolicy policy)
 {
     if (policy.RequireAudience)
     {
         validationControl &= unchecked ((byte)~TokenValidationPolicy.MissingAudienceMask);
         var audiencesBinary = policy.RequiredAudiencesBinary;
         for (int i = 0; i < audiencesBinary.Length; i++)
         {
             if (reader.ValueTextEquals(audiencesBinary[i]))
             {
                 validationControl &= unchecked ((byte)~TokenValidationPolicy.AudienceMask);
                 break;
             }
         }
     }
 }
Esempio n. 10
0
        private static bool TryCheckNotBefore(ref Utf8JsonReader reader, ref byte validationControl, TokenValidationPolicy policy)
        {
            if (!reader.TryGetInt64(out long nbf))
            {
                return(false);
            }

            // the 'nbf' claim is not common. A 2nd call to EpochTime.UtcNow should be rare.
            if (nbf > EpochTime.UtcNow + policy.ClockSkew &&
                (policy.Control & TokenValidationPolicy.ExpirationTimeMask) == TokenValidationPolicy.ExpirationTimeMask)
            {
                validationControl |= TokenValidationPolicy.NotBeforeMask;
            }

            return(true);
        }
Esempio n. 11
0
        private static bool TryCheckExpirationTime(ref Utf8JsonReader reader, ref byte validationControl, TokenValidationPolicy policy)
        {
            if (policy.RequireExpirationTime)
            {
                if (!reader.TryGetInt64(out long exp))
                {
                    return(false);
                }

                validationControl &= unchecked ((byte)~TokenValidationPolicy.ExpirationTimeRequiredMask);
                if (exp + policy.ClockSkew >= EpochTime.UtcNow)
                {
                    validationControl &= unchecked ((byte)~TokenValidationPolicy.ExpirationTimeMask);
                }
            }

            return(true);
        }
Esempio n. 12
0
 private static void CheckIssuer(ref Utf8JsonReader reader, ref byte validationControl, TokenValidationPolicy policy)
 {
     if (policy.RequireIssuer)
     {
         validationControl &= unchecked ((byte)~TokenValidationPolicy.MissingIssuerMask);
         var issuerBinary = policy.RequiredIssuersBinary;
         for (int i = 0; i < issuerBinary.Length; i++)
         {
             if (reader.ValueTextEquals(issuerBinary[i]))
             {
                 validationControl &= unchecked ((byte)~TokenValidationPolicy.IssuerMask);
                 break;
             }
         }
     }
 }
Esempio n. 13
0
        /// <summary>
        /// Parses the UTF-8 <paramref name="buffer"/> as JSON and returns a <see cref="JwtHeader"/>.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="policy"></param>
        public static JwtHeader ParseHeader(ReadOnlySpan <byte> buffer, TokenValidationPolicy policy)
        {
            Utf8JsonReader reader = new Utf8JsonReader(buffer, isFinalBlock: true, state: default);

            if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
            {
                ThrowHelper.ThrowFormatException_MalformedJson();
            }

            var header = new JwtHeader();

            while (reader.Read())
            {
                if (!(reader.TokenType is JsonTokenType.PropertyName))
                {
                    break;
                }

                var name = reader.ValueSpan /* reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan */;
                reader.Read();
                var type = reader.TokenType;


                if (name.Length == 3)
                {
                    if (reader.TokenType == JsonTokenType.String)
                    {
                        var refName = IntegerMarshal.ReadUInt24(name);
                        switch (refName)
                        {
                        case Alg:
                            var alg = reader.ValueSpan /* reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan */;
                            if (SignatureAlgorithm.TryParse(alg, out var signatureAlgorithm))
                            {
                                header.SignatureAlgorithm = signatureAlgorithm;
                            }
                            else if (KeyManagementAlgorithm.TryParse(alg, out var keyManagementAlgorithm))
                            {
                                header.KeyManagementAlgorithm = keyManagementAlgorithm;
                            }
                            else if (SignatureAlgorithm.TryParseSlow(ref reader, out signatureAlgorithm))
                            {
                                header.SignatureAlgorithm = signatureAlgorithm;
                            }
                            else if (KeyManagementAlgorithm.TryParseSlow(ref reader, out keyManagementAlgorithm))
                            {
                                header.KeyManagementAlgorithm = keyManagementAlgorithm;
                            }
                            else
                            {
                                header.SignatureAlgorithm = SignatureAlgorithm.Create(reader.GetString());
                            }

                            continue;

                        case Enc:
                            var enc = reader.ValueSpan /* reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan */;
                            if (EncryptionAlgorithm.TryParse(enc, out var encryptionAlgorithm))
                            {
                                header.EncryptionAlgorithm = encryptionAlgorithm;
                            }
                            else if (EncryptionAlgorithm.TryParseSlow(ref reader, out encryptionAlgorithm))
                            {
                                header.EncryptionAlgorithm = encryptionAlgorithm;
                            }
                            else
                            {
                                header.EncryptionAlgorithm = EncryptionAlgorithm.Create(reader.GetString());
                            }

                            continue;

                        case Zip:
                            var zip = reader.ValueSpan /* reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan */;
                            if (CompressionAlgorithm.TryParse(zip, out var compressionAlgorithm))
                            {
                                header.CompressionAlgorithm = compressionAlgorithm;
                            }
                            else if (CompressionAlgorithm.TryParseSlow(ref reader, out compressionAlgorithm))
                            {
                                header.CompressionAlgorithm = compressionAlgorithm;
                            }
                            else
                            {
                                header.CompressionAlgorithm = CompressionAlgorithm.Create(reader.GetString());
                            }

                            continue;

                        case Cty:
                            header.Cty = reader.GetString();
                            continue;

                        case Typ:
                            header.Typ = reader.GetString();
                            continue;

                        case Kid:
                            header.Kid = reader.GetString();
                            continue;
                        }
                    }
                }
                else if (name.Length == 4)
                {
                    if (reader.TokenType == JsonTokenType.StartArray && IntegerMarshal.ReadUInt32(name) == Crit)
                    {
                        var handlers = policy.CriticalHandlers;
                        if (handlers.Count != 0)
                        {
                            var criticalHeaderHandlers = new List <KeyValuePair <string, ICriticalHeaderHandler> >(handlers.Count);
                            var criticals = new List <JwtValue>();
                            while (reader.Read() && reader.TokenType == JsonTokenType.String)
                            {
                                string criticalHeader = reader.GetString();
                                criticals.Add(new JwtValue(criticalHeader));
                                if (handlers.TryGetValue(criticalHeader, out var handler))
                                {
                                    criticalHeaderHandlers.Add(new KeyValuePair <string, ICriticalHeaderHandler>(criticalHeader, handler));
                                }
                                else
                                {
                                    criticalHeaderHandlers.Add(new KeyValuePair <string, ICriticalHeaderHandler>(criticalHeader, null !));
                                }
                            }

                            header.CriticalHeaderHandlers = criticalHeaderHandlers;

                            if (reader.TokenType != JsonTokenType.EndArray)
                            {
                                ThrowHelper.ThrowFormatException_MalformedJson("The 'crit' header parameter must be an array of string.");
                            }

                            header.Inner.Add(name, new JwtArray(criticals));
                        }
                        else
                        {
                            var criticals = new List <JwtValue>();
                            while (reader.Read() && reader.TokenType == JsonTokenType.String)
                            {
                                string criticalHeader = reader.GetString();
                                criticals.Add(new JwtValue(criticalHeader));
                            }

                            if (reader.TokenType != JsonTokenType.EndArray)
                            {
                                ThrowHelper.ThrowFormatException_MalformedJson("The 'crit' header parameter must be an array of string.");
                            }

                            header.Inner.Add(name, new JwtArray(criticals));
                        }

                        continue;
                    }
                }


                switch (type)
                {
                case JsonTokenType.StartObject:
                    header.Inner.Add(name, JsonParser.ReadJsonObject(ref reader));
                    break;

                case JsonTokenType.StartArray:
                    header.Inner.Add(name, JsonParser.ReadJsonArray(ref reader));
                    break;

                case JsonTokenType.String:
                    header.Inner.Add(name, reader.GetString());
                    break;

                case JsonTokenType.True:
                    header.Inner.Add(name, true);
                    break;

                case JsonTokenType.False:
                    header.Inner.Add(name, false);
                    break;

                case JsonTokenType.Null:
                    header.Inner.Add(name);
                    break;

                case JsonTokenType.Number:
                    if (reader.TryGetInt64(out long longValue))
                    {
                        header.Inner.Add(name, longValue);
                    }
                    else
                    {
                        header.Inner.Add(name, reader.GetDouble());
                    }
                    break;

                default:
                    ThrowHelper.ThrowFormatException_MalformedJson();
                    break;
                }
            }

            if (!(reader.TokenType is JsonTokenType.EndObject))
            {
                ThrowHelper.ThrowFormatException_MalformedJson();
            }

            return(header);
        }
Esempio n. 14
0
        /// <summary>
        /// Parses the UTF-8 <paramref name="buffer"/> as JSON and returns a <see cref="JwtPayload"/>.
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="policy"></param>
        public static JwtPayload ParsePayload(ReadOnlySpan <byte> buffer, TokenValidationPolicy policy)
        {
            Utf8JsonReader reader = new Utf8JsonReader(buffer, isFinalBlock: true, state: default);

            if (!reader.Read() || reader.TokenType != JsonTokenType.StartObject)
            {
                ThrowHelper.ThrowFormatException_MalformedJson();
            }

            var  payload = new JwtPayload();
            byte control = policy.ValidationControl;

            while (reader.Read() && reader.TokenType is JsonTokenType.PropertyName)
            {
                var name = reader.ValueSpan /* reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan */;
                reader.Read();
                var type = reader.TokenType;
                if (name.Length == 3)
                {
                    uint nameValue = IntegerMarshal.ReadUInt24(name);
                    switch (nameValue)
                    {
                    case Aud:
                        if (type == JsonTokenType.String)
                        {
                            if (policy.RequireAudience)
                            {
                                var audiencesBinary = policy.RequiredAudiencesBinary;
                                var audiences       = policy.RequiredAudiences;
                                for (int i = 0; i < audiencesBinary.Length; i++)
                                {
                                    if (reader.ValueTextEquals(audiencesBinary[i]))
                                    {
                                        payload.Aud = new[] { audiences[i] };
                                        control    &= unchecked ((byte)~TokenValidationPolicy.AudienceFlag);
                                        break;
                                    }
                                }

                                control &= unchecked ((byte)~JwtPayload.MissingAudienceFlag);
                            }
                            else
                            {
                                payload.Aud = new[] { reader.GetString() };
                            }
                        }
                        else if (type == JsonTokenType.StartArray)
                        {
                            if (policy.RequireAudience)
                            {
                                var audiences = new List <string>();
                                while (reader.Read() && reader.TokenType == JsonTokenType.String)
                                {
                                    var requiredAudiences = policy.RequiredAudiencesBinary;
                                    for (int i = 0; i < requiredAudiences.Length; i++)
                                    {
                                        if (reader.ValueTextEquals(requiredAudiences[i]))
                                        {
                                            control &= unchecked ((byte)~TokenValidationPolicy.AudienceFlag);
                                            break;
                                        }
                                    }

                                    audiences.Add(reader.GetString());
                                    control &= unchecked ((byte)~JwtPayload.MissingAudienceFlag);
                                }

                                if (reader.TokenType != JsonTokenType.EndArray)
                                {
                                    ThrowHelper.ThrowFormatException_MalformedJson("The 'aud' claim must be an array of string or a string.");
                                }

                                payload.Aud = audiences.ToArray();
                            }
                            else
                            {
                                payload.Aud = JsonParser.ReadStringArray(ref reader);
                            }
                        }
                        else
                        {
                            ThrowHelper.ThrowFormatException_MalformedJson("The 'aud' claim must be an array of string or a string.");
                        }

                        continue;

                    case Iss:
                        if (policy.RequireIssuer)
                        {
                            if (reader.ValueTextEquals(policy.RequiredIssuerBinary))
                            {
                                payload.Iss = policy.RequiredIssuer;
                                control    &= unchecked ((byte)~TokenValidationPolicy.IssuerFlag);
                            }
                            else
                            {
                                control &= unchecked ((byte)~JwtPayload.MissingIssuerFlag);
                            }
                        }
                        else
                        {
                            payload.Iss = reader.GetString();
                        }

                        continue;

                    case Exp:
                        if (!reader.TryGetInt64(out long longValue))
                        {
                            ThrowHelper.ThrowFormatException_MalformedJson("The claim 'exp' must be an integral number.");
                        }

                        if (policy.RequireExpirationTime)
                        {
                            control &= unchecked ((byte)~JwtPayload.MissingExpirationFlag);
                        }

                        if (longValue >= EpochTime.UtcNow - policy.ClockSkew)
                        {
                            control &= unchecked ((byte)~JwtPayload.ExpiredFlag);
                        }

                        payload.Exp = longValue;
                        continue;

                    case Iat:
                        if (!reader.TryGetInt64(out longValue))
                        {
                            ThrowHelper.ThrowFormatException_MalformedJson("The claim 'iat' must be an integral number.");
                        }

                        payload.Iat = longValue;
                        continue;

                    case Nbf:
                        if (!reader.TryGetInt64(out longValue))
                        {
                            ThrowHelper.ThrowFormatException_MalformedJson("The claim 'nbf' must be an integral number.");
                        }

                        // the 'nbf' claim is not common. The 2nd call to EpochTime.UtcNow should be rare.
                        if (longValue <= EpochTime.UtcNow + policy.ClockSkew)
                        {
                            control &= unchecked ((byte)~JwtPayload.NotYetFlag);
                        }

                        payload.Nbf = longValue;
                        continue;

                    case Jti:
                        payload.Jti = reader.GetString();
                        continue;

                    case Sub:
                        payload.Sub = reader.GetString();
                        continue;
                    }
                }

                switch (type)
                {
                case JsonTokenType.StartObject:
                    payload.Inner.Add(name, JsonParser.ReadJsonObject(ref reader));
                    break;

                case JsonTokenType.StartArray:
                    payload.Inner.Add(name, JsonParser.ReadJsonArray(ref reader));
                    break;

                case JsonTokenType.String:
                    payload.Inner.Add(name, reader.GetString());
                    break;

                case JsonTokenType.True:
                    payload.Inner.Add(name, true);
                    break;

                case JsonTokenType.False:
                    payload.Inner.Add(name, false);
                    break;

                case JsonTokenType.Null:
                    payload.Inner.Add(name);
                    break;

                case JsonTokenType.Number:
                    long longValue;

                    if (reader.TryGetInt64(out longValue))
                    {
                        payload.Inner.Add(name, longValue);
                    }
                    else
                    {
                        payload.Inner.Add(name, reader.GetDouble());
                    }
                    break;

                default:
                    ThrowHelper.ThrowFormatException_MalformedJson();
                    break;
                }
            }

            if (!(reader.TokenType is JsonTokenType.EndObject))
            {
                ThrowHelper.ThrowFormatException_MalformedJson();
            }

            payload.ValidationControl = control;

            return(payload);
        }
Esempio n. 15
0
 public TokenValidationResult TryReadToken(ReadOnlySpan <byte> utf8Token, TokenValidationPolicy policy)
 => throw new NotImplementedException();
Esempio n. 16
0
 public TokenValidationResult TryReadToken(string token, TokenValidationPolicy policy)
 => throw new NotImplementedException();