diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index 8a859dede8..c7cc2fd597 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -1,25 +1,27 @@ -THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +# THIRD-PARTY SOFTWARE NOTICES AND INFORMATION This project incorporates components from the projects listed below. The original copyright notices and the licenses under which Etienne Baudoux received such components are set forth below. Etienne Baudoux reserves all rights not expressly granted herein, whether by implication, estoppel or otherwise. -1. Newtonsoft.Json (https://github.com/JamesNK/Newtonsoft.Json) -2. GitInfo (https://github.com/devlooped/GitInfo) -3. Windows Community Toolkit (https://github.com/CommunityToolkit/WindowsCommunityToolkit) -4. YamlDotNet (https://github.com/aaubry/YamlDotNet) -5. Fluent UI System Icons (https://github.com/microsoft/fluentui-system-icons) -6. Monaco Editor (https://github.com/Microsoft/monaco-editor) -7. Monaco Editor UWP (https://github.com/hawkerm/monaco-editor-uwp) -8. Observable Vector (https://github.com/jamesqo/observable-vector) -9. Azure Active Directory IdentityModel Extensions for .NET (https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet) -10. Markdig (https://github.com/xoofx/markdig) -11. github-markdown-css (https://github.com/sindresorhus/github-markdown-css) -12. Efficient Compression Tool (https://github.com/fhanau/Efficient-Compression-Tool) -13. NLipsum (https://github.com/alexcpendleton/NLipsum) -14. SQL Formatter (https://github.com/zeroturnaround/sql-formatter) -15. Cronos (https://github.com/HangfireIO/Cronos) - -Newtonsoft.Json NOTICES AND INFORMATION BEGIN HERE +1. [Newtonsoft.Json](#newtonsoftjson) () +2. [GitInfo](#gitinfo) () +3. [Windows Community Toolkit](#windows-community-toolkit) () +4. [YamlDotNet](#yamldotnet) () +5. [Fluent UI System Icons](#fluent-ui-system-icons) () +6. [Monaco Editor](#monaco-editor) () +7. [Monaco Editor UWP](#monaco-editor-uwp) () +8. [Observable Vector](#observable-vector) () +9. [Azure Active Directory IdentityModel Extensions for .NET](#azure-active-directory-identitymodel-extensions-for-net) () +10. [Markdig](#markdig) () +11. [github-markdown-css](#github-markdown-css) () +12. [Efficient Compression Tool](#efficient-compression-tool) () +13. [NLipsum](#nlipsum) () +14. [SQL Formatter](#sql-formatter) () +15. [Cronos](#cronos) () +16. [BouncyCastle](#bouncy-castle) () + +Newtonsoft.Json ========================================= + The MIT License (MIT) Copyright (c) 2007 James Newton-King @@ -41,8 +43,9 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -GitInfo NOTICES AND INFORMATION BEGIN HERE +GitInfo ========================================= + The MIT License (MIT) Copyright (c) Daniel Cazzulino and Contributors @@ -65,15 +68,14 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Windows Community Toolkit NOTICES AND INFORMATION BEGIN HERE +Windows Community Toolkit ========================================= -# Windows Community Toolkit Copyright © .NET Foundation and Contributors All rights reserved. -## MIT License (MIT) +# MIT License (MIT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -81,8 +83,9 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -YamlDotNet NOTICES AND INFORMATION BEGIN HERE +YamlDotNet ========================================= + Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Antoine Aubry and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of @@ -103,8 +106,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Fluent UI System Icons NOTICES AND INFORMATION BEGIN HERE +Fluent UI System Icons ========================================= + MIT License Copyright (c) 2020 Microsoft Corporation @@ -127,8 +131,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Monaco Editor NOTICES AND INFORMATION BEGIN HERE +Monaco Editor ========================================= + The MIT License (MIT) Copyright (c) 2016 - present Microsoft Corporation @@ -139,8 +144,9 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Monaco Editor UWP NOTICES AND INFORMATION BEGIN HERE +Monaco Editor UWP ========================================= + Copyright 2017 Michael A. Hawker Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -149,8 +155,9 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Observable Vector NOTICES AND INFORMATION BEGIN HERE +Observable Vector ========================================= + The MIT License (MIT) Copyright (c) 2016 James Ko @@ -173,8 +180,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Azure Active Directory IdentityModel Extensions for .NET NOTICES AND INFORMATION BEGIN HERE +Azure Active Directory IdentityModel Extensions for .NET ========================================= + The MIT License (MIT) Copyright (c) Microsoft Corporation @@ -197,37 +205,39 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Markdig NOTICES AND INFORMATION BEGIN HERE +Markdig ========================================= + Copyright (c) 2018-2019, Alexandre Mutel All rights reserved. Redistribution and use in source and binary forms, with or without modification , are permitted provided that the following conditions are met: -1. Redistributions of source code must retain the above copyright notice, this +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -github-markdown-css NOTICES AND INFORMATION BEGIN HERE +github-markdown-css ========================================= + MIT License -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +Copyright (c) Sindre Sorhus (<) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -235,8 +245,9 @@ The above copyright notice and this permission notice shall be included in all c THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Efficient Compression Tool NOTICES AND INFORMATION BEGIN HERE +Efficient Compression Tool ========================================= + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -439,8 +450,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -NLipsum NOTICES AND INFORMATION BEGIN HERE +NLipsum ========================================= + The MIT License (MIT) Copyright (c) 2015 Alex Pendleton @@ -462,8 +474,9 @@ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -SQL Formatter NOTICES AND INFORMATION BEGIN HERE +SQL Formatter ========================================= + The MIT License (MIT) Copyright (c) 2016-2020 ZeroTurnaround LLC @@ -487,8 +500,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Cronos NOTICES AND INFORMATION BEGIN HERE +Cronos ========================================= + The MIT License (MIT) Copyright (c) 2017 Hangfire OÜ @@ -509,4 +523,20 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. + +Bouncy Castle +========================================= + +Please note this should be read in the same way as the [MIT license](<). + +Please also note this licensing model is made possible through funding from [donations](<) and the sale of [support contracts](<). + +LICENSE +Copyright (c) 2000 - 2021 The Legion of the Bouncy Castle Inc. (<) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 16c0495139..125d3ec8fe 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -37,5 +37,6 @@ 11.2.1 6.13.1 1.1.0 + 1.9.0 \ No newline at end of file diff --git a/src/dev/impl/DevToys/Core/QueueWorkerViewModelBase.cs b/src/dev/impl/DevToys/Core/QueueWorkerViewModelBaseAsync.cs similarity index 100% rename from src/dev/impl/DevToys/Core/QueueWorkerViewModelBase.cs rename to src/dev/impl/DevToys/Core/QueueWorkerViewModelBaseAsync.cs diff --git a/src/dev/impl/DevToys/DevToys.csproj b/src/dev/impl/DevToys/DevToys.csproj index 0c93d4b5de..7513d0ee6d 100644 --- a/src/dev/impl/DevToys/DevToys.csproj +++ b/src/dev/impl/DevToys/DevToys.csproj @@ -28,8 +28,9 @@ - + + @@ -74,12 +75,20 @@ + + + + + + + + FileSelector.xaml @@ -114,6 +123,14 @@ + + + + + + + + @@ -173,6 +190,12 @@ GZipEncoderDecoderToolPage.xaml + + JwtDecoderControl.xaml + + + JwtEncoderControl.xaml + XmlFormatterToolPage.xaml @@ -553,6 +576,14 @@ Designer + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + MSBuild:Compile Designer @@ -742,6 +773,7 @@ + diff --git a/src/dev/impl/DevToys/Helpers/Base64Helper.cs b/src/dev/impl/DevToys/Helpers/Base64Helper.cs new file mode 100644 index 0000000000..8d0b4fa262 --- /dev/null +++ b/src/dev/impl/DevToys/Helpers/Base64Helper.cs @@ -0,0 +1,76 @@ +#nullable enable + +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace DevToys.Helpers +{ + internal static class Base64Helper + { + internal static bool IsBase64DataStrict(string? data) + { + if (string.IsNullOrWhiteSpace(data)) + { + return false; + } + + data = data!.Trim(); + + if (data.Length % 4 != 0) + { + return false; + } + + if (new Regex(@"[^A-Z0-9+/=]", RegexOptions.IgnoreCase).IsMatch(data)) + { + return false; + } + + int equalIndex = data.IndexOf('='); + int length = data.Length; + + if (!(equalIndex == -1 || equalIndex == length - 1 || (equalIndex == length - 2 && data[length - 1] == '='))) + { + return false; + } + + string? decoded; + + try + { + byte[]? decodedData = Convert.FromBase64String(data); + decoded = Encoding.UTF8.GetString(decodedData); + } + catch (Exception) + { + return false; + } + + //check for special chars that you know should not be there + char current; + for (int i = 0; i < decoded.Length; i++) + { + current = decoded[i]; + if (current == 65533) + { + return false; + } + +#pragma warning disable IDE0078 // Use pattern matching + if (!(current == 0x9 + || current == 0xA + || current == 0xD + || (current >= 0x20 && current <= 0xD7FF) + || (current >= 0xE000 && current <= 0xFFFD) + || (current >= 0x10000 && current <= 0x10FFFF))) +#pragma warning restore IDE0078 // Use pattern matching + { + return false; + } + } + + return true; + } + } +} diff --git a/src/dev/impl/DevToys/Helpers/JwtHelper.cs b/src/dev/impl/DevToys/Helpers/JwtHelper.cs index 9dd7bddffe..f9446ab805 100644 --- a/src/dev/impl/DevToys/Helpers/JwtHelper.cs +++ b/src/dev/impl/DevToys/Helpers/JwtHelper.cs @@ -7,6 +7,9 @@ namespace DevToys.Helpers { internal static class JwtHelper { + private static readonly string AuthorizationHeader = "Authorization:"; + private static readonly string BearerScheme = "Bearer"; + /// /// Detects whether the given string is a JWT Token or not. /// @@ -19,10 +22,20 @@ internal static bool IsValid(string? input) input = input!.Trim(); + if (input.StartsWith(AuthorizationHeader)) + { + input = input.Remove(0, AuthorizationHeader.Length).Trim(); + } + + if (input.StartsWith(BearerScheme)) + { + input = input.Remove(0, BearerScheme.Length).Trim(); + } + try { var handler = new JwtSecurityTokenHandler(); - JwtSecurityToken jwtSecurityToken = handler.ReadJwtToken(input); + JwtSecurityToken jwtSecurityToken = handler.ReadJwtToken(input.Trim()); return jwtSecurityToken is not null; } catch (Exception) //some other exception diff --git a/src/dev/impl/DevToys/LanguageManager.cs b/src/dev/impl/DevToys/LanguageManager.cs index 31ac99e863..34fcb706ae 100644 --- a/src/dev/impl/DevToys/LanguageManager.cs +++ b/src/dev/impl/DevToys/LanguageManager.cs @@ -1582,9 +1582,9 @@ public class JwtDecoderEncoderStrings : ObservableObject public string MenuDisplayName => _resources.GetString("MenuDisplayName"); /// - /// Gets the resource HeaderLabel. + /// Gets the resource JwtHeaderLabel. /// - public string HeaderLabel => _resources.GetString("HeaderLabel"); + public string JwtHeaderLabel => _resources.GetString("JwtHeaderLabel"); /// /// Gets the resource JwtTokenLabel. @@ -1592,24 +1592,219 @@ public class JwtDecoderEncoderStrings : ObservableObject public string JwtTokenLabel => _resources.GetString("JwtTokenLabel"); /// - /// Gets the resource PayloadLabel. + /// Gets the resource JwtPayloadLabel. /// - public string PayloadLabel => _resources.GetString("PayloadLabel"); + public string JwtPayloadLabel => _resources.GetString("JwtPayloadLabel"); /// /// Gets the resource Description. /// public string Description => _resources.GetString("Description"); + /// + /// Gets the resource SearchKeywords. + /// + public string SearchKeywords => _resources.GetString("SearchKeywords"); + + /// + /// Gets the resource Algorithm. + /// + public string Algorithm => _resources.GetString("Algorithm"); + + /// + /// Gets the resource DecodeSwitchModeLabel. + /// + public string DecodeSwitchModeLabel => _resources.GetString("DecodeSwitchModeLabel"); + + /// + /// Gets the resource DecodeValidateTokenLabel. + /// + public string DecodeValidateTokenLabel => _resources.GetString("DecodeValidateTokenLabel"); + + /// + /// Gets the resource DecodeValidateTokenNoLabel. + /// + public string DecodeValidateTokenNoLabel => _resources.GetString("DecodeValidateTokenNoLabel"); + + /// + /// Gets the resource DecodeValidateTokenYesLabel. + /// + public string DecodeValidateTokenYesLabel => _resources.GetString("DecodeValidateTokenYesLabel"); + + /// + /// Gets the resource EncodeExpirationTitle. + /// + public string EncodeExpirationTitle => _resources.GetString("EncodeExpirationTitle"); + + /// + /// Gets the resource EncodeExpirationMonthLabel. + /// + public string EncodeExpirationMonthLabel => _resources.GetString("EncodeExpirationMonthLabel"); + + /// + /// Gets the resource EncodeExpirationYearLabel. + /// + public string EncodeExpirationYearLabel => _resources.GetString("EncodeExpirationYearLabel"); + + /// + /// Gets the resource EncodeHashingTitle. + /// + public string EncodeHashingTitle => _resources.GetString("EncodeHashingTitle"); + + /// + /// Gets the resource EncodeSettingsTitle. + /// + public string EncodeSettingsTitle => _resources.GetString("EncodeSettingsTitle"); + + /// + /// Gets the resource EncodeSwitchModeLabel. + /// + public string EncodeSwitchModeLabel => _resources.GetString("EncodeSwitchModeLabel"); + + /// + /// Gets the resource SettingsSwitchModeLabel. + /// + public string SettingsSwitchModeLabel => _resources.GetString("SettingsSwitchModeLabel"); + + /// + /// Gets the resource SettingsTitle. + /// + public string SettingsTitle => _resources.GetString("SettingsTitle"); + + /// + /// Gets the resource EncodeDefaultTimeTitle. + /// + public string EncodeDefaultTimeTitle => _resources.GetString("EncodeDefaultTimeTitle"); + + /// + /// Gets the resource EncodeExpirationDaysLabel. + /// + public string EncodeExpirationDaysLabel => _resources.GetString("EncodeExpirationDaysLabel"); + + /// + /// Gets the resource EncodeExpirationHoursLabel. + /// + public string EncodeExpirationHoursLabel => _resources.GetString("EncodeExpirationHoursLabel"); + + /// + /// Gets the resource EncodeExpirationMinutesLabel. + /// + public string EncodeExpirationMinutesLabel => _resources.GetString("EncodeExpirationMinutesLabel"); + + /// + /// Gets the resource PrivateKeyLabel. + /// + public string PrivateKeyLabel => _resources.GetString("PrivateKeyLabel"); + + /// + /// Gets the resource SignatureLabel. + /// + public string SignatureLabel => _resources.GetString("SignatureLabel"); + + /// + /// Gets the resource JwtIsValidMessage. + /// + public string JwtIsValidMessage => _resources.GetString("JwtIsValidMessage"); + + /// + /// Gets the resource InvalidPublicKeyError. + /// + public string InvalidPublicKeyError => _resources.GetString("InvalidPublicKeyError"); + + /// + /// Gets the resource JwtInValidMessage. + /// + public string JwtInValidMessage => _resources.GetString("JwtInValidMessage"); + + /// + /// Gets the resource DecodeValidateActorLabel. + /// + public string DecodeValidateActorLabel => _resources.GetString("DecodeValidateActorLabel"); + + /// + /// Gets the resource DecodeValidateAudienceLabel. + /// + public string DecodeValidateAudienceLabel => _resources.GetString("DecodeValidateAudienceLabel"); + + /// + /// Gets the resource DecodeValidateIssuerLabel. + /// + public string DecodeValidateIssuerLabel => _resources.GetString("DecodeValidateIssuerLabel"); + + /// + /// Gets the resource DecodeValidateLifetimeLabel. + /// + public string DecodeValidateLifetimeLabel => _resources.GetString("DecodeValidateLifetimeLabel"); + + /// + /// Gets the resource ValidAudiencesLabel. + /// + public string ValidAudiencesLabel => _resources.GetString("ValidAudiencesLabel"); + + /// + /// Gets the resource ValidIssuersLabel. + /// + public string ValidIssuersLabel => _resources.GetString("ValidIssuersLabel"); + + /// + /// Gets the resource DecodeValidationSettingsDescription. + /// + public string DecodeValidationSettingsDescription => _resources.GetString("DecodeValidationSettingsDescription"); + + /// + /// Gets the resource DecodeValidationSettingsTitle. + /// + public string DecodeValidationSettingsTitle => _resources.GetString("DecodeValidationSettingsTitle"); + + /// + /// Gets the resource ValidAudiencesError. + /// + public string ValidAudiencesError => _resources.GetString("ValidAudiencesError"); + + /// + /// Gets the resource ValidIssuersError. + /// + public string ValidIssuersError => _resources.GetString("ValidIssuersError"); + + /// + /// Gets the resource PublicKeyIsPrivateKeyError. + /// + public string PublicKeyIsPrivateKeyError => _resources.GetString("PublicKeyIsPrivateKeyError"); + + /// + /// Gets the resource EncodeAudienceLabel. + /// + public string EncodeAudienceLabel => _resources.GetString("EncodeAudienceLabel"); + + /// + /// Gets the resource EncodeIssuerLabel. + /// + public string EncodeIssuerLabel => _resources.GetString("EncodeIssuerLabel"); + + /// + /// Gets the resource EncodeSettingsDescription. + /// + public string EncodeSettingsDescription => _resources.GetString("EncodeSettingsDescription"); + + /// + /// Gets the resource InvalidPrivateKeyError. + /// + public string InvalidPrivateKeyError => _resources.GetString("InvalidPrivateKeyError"); + + /// + /// Gets the resource PublicKeyLabel. + /// + public string PublicKeyLabel => _resources.GetString("PublicKeyLabel"); + /// /// Gets the resource SearchDisplayName. /// public string SearchDisplayName => _resources.GetString("SearchDisplayName"); /// - /// Gets the resource SearchKeywords. + /// Gets the resource InvalidSignatureError. /// - public string SearchKeywords => _resources.GetString("SearchKeywords"); + public string InvalidSignatureError => _resources.GetString("InvalidSignatureError"); } public class LoremIpsumGeneratorStrings : ObservableObject diff --git a/src/dev/impl/DevToys/Models/JwtAlgorithm.cs b/src/dev/impl/DevToys/Models/JwtAlgorithm.cs new file mode 100644 index 0000000000..49ad9edf17 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtAlgorithm.cs @@ -0,0 +1,18 @@ +namespace DevToys.Models +{ + public enum JwtAlgorithm + { + HS256, + HS384, + HS512, + RS256, + RS384, + RS512, + ES256, + ES384, + ES512, + PS256, + PS384, + PS512 + } +} diff --git a/src/dev/impl/DevToys/Models/JwtAlgorithmDisplayPair.cs b/src/dev/impl/DevToys/Models/JwtAlgorithmDisplayPair.cs new file mode 100644 index 0000000000..7bae9f7df6 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtAlgorithmDisplayPair.cs @@ -0,0 +1,32 @@ +using System; + +namespace DevToys.Models +{ + public sealed class JwtAlgorithmDisplayPair : IEquatable + { + public static readonly JwtAlgorithmDisplayPair HS256 = new(nameof(HS256), JwtAlgorithm.HS256); + public static readonly JwtAlgorithmDisplayPair HS384 = new(nameof(HS384), JwtAlgorithm.HS384); + public static readonly JwtAlgorithmDisplayPair HS512 = new(nameof(HS512), JwtAlgorithm.HS512); + public static readonly JwtAlgorithmDisplayPair RS256 = new(nameof(RS256), JwtAlgorithm.RS256); + public static readonly JwtAlgorithmDisplayPair RS384 = new(nameof(RS384), JwtAlgorithm.RS384); + public static readonly JwtAlgorithmDisplayPair RS512 = new(nameof(RS512), JwtAlgorithm.RS512); + public static readonly JwtAlgorithmDisplayPair ES256 = new(nameof(ES256), JwtAlgorithm.ES256); + public static readonly JwtAlgorithmDisplayPair ES384 = new(nameof(ES384), JwtAlgorithm.ES384); + public static readonly JwtAlgorithmDisplayPair ES512 = new(nameof(ES512), JwtAlgorithm.ES512); + public static readonly JwtAlgorithmDisplayPair PS256 = new(nameof(PS256), JwtAlgorithm.PS256); + public static readonly JwtAlgorithmDisplayPair PS384 = new(nameof(PS384), JwtAlgorithm.PS384); + public static readonly JwtAlgorithmDisplayPair PS512 = new(nameof(PS512), JwtAlgorithm.PS512); + + public string DisplayName { get; } + + public JwtAlgorithm Value { get; } + + private JwtAlgorithmDisplayPair(string displayName, JwtAlgorithm value) + { + DisplayName = displayName; + Value = value; + } + + public bool Equals(JwtAlgorithmDisplayPair other) => other.Value == Value; + } +} diff --git a/src/dev/impl/DevToys/Models/JwtDecoderEncoder/DecoderParameters.cs b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/DecoderParameters.cs new file mode 100644 index 0000000000..213193b6e4 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/DecoderParameters.cs @@ -0,0 +1,20 @@ +#nullable enable + +using DevToys; + +namespace DevToys.Models.JwtDecoderEncoder +{ + public record DecoderParameters + { + public bool ValidateSignature { get; set; } + + public bool ValidateActor { get; set; } + + public bool ValidateLifetime { get; set; } + + public bool ValidateIssuer { get; set; } + + public bool ValidateAudience { get; set; } + + } +} diff --git a/src/dev/impl/DevToys/Models/JwtDecoderEncoder/EncoderParameters.cs b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/EncoderParameters.cs new file mode 100644 index 0000000000..b1d6946ea9 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/EncoderParameters.cs @@ -0,0 +1,17 @@ +#nullable enable + +using DevToys; + +namespace DevToys.Models.JwtDecoderEncoder +{ + public record EncoderParameters + { + public bool HasExpiration { get; set; } + + public bool HasAudience { get; set; } + + public bool HasIssuer { get; set; } + + public bool HasDefaultTime { get; set; } + } +} diff --git a/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenParameters.cs b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenParameters.cs new file mode 100644 index 0000000000..5699496de8 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenParameters.cs @@ -0,0 +1,37 @@ +#nullable enable + +using System.Collections.Generic; +using System.Linq; + +namespace DevToys.Models.JwtDecoderEncoder +{ + public class TokenParameters + { + public string? Token { get; set; } + + public string? Payload { get; set; } + + public string? Signature { get; set; } + + public string? PublicKey { get; set; } + + public string? PrivateKey { get; set; } + + public JwtAlgorithm TokenAlgorithm { get; set; } + + public int ExpirationYear { get; set; } + + public int ExpirationMonth { get; set; } + + public int ExpirationDay { get; set; } + + public int ExpirationHour { get; set; } + + public int ExpirationMinute { get; set; } + + public HashSet ValidIssuers { get; set; } = new HashSet(); + + public HashSet ValidAudiences { get; set; } = new HashSet(); + + } +} diff --git a/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResult.cs b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResult.cs new file mode 100644 index 0000000000..eeba1aa86b --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResult.cs @@ -0,0 +1,22 @@ +#nullable enable + + +namespace DevToys.Models.JwtDecoderEncoder +{ + public class TokenResult + { + public string? Token { get; set; } + + public string? Header { get; set; } + + public string? Payload { get; set; } + + public string? Signature { get; set; } + + public string? PublicKey { get; set; } + + public string? PrivateKey { get; set; } + + public JwtAlgorithm TokenAlgorithm { get; set; } + } +} diff --git a/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResultErrorEventArgs.cs b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResultErrorEventArgs.cs new file mode 100644 index 0000000000..c8dd6e7000 --- /dev/null +++ b/src/dev/impl/DevToys/Models/JwtDecoderEncoder/TokenResultErrorEventArgs.cs @@ -0,0 +1,14 @@ +#nullable enable + +using System; +using Microsoft.UI.Xaml.Controls; + +namespace DevToys.Models.JwtDecoderEncoder +{ + public class TokenResultErrorEventArgs : EventArgs + { + public string Message { get; set; } = string.Empty; + + public InfoBarSeverity Severity { get; set; } + } +} diff --git a/src/dev/impl/DevToys/Models/ValidationBase.cs b/src/dev/impl/DevToys/Models/ValidationBase.cs new file mode 100644 index 0000000000..2546222d14 --- /dev/null +++ b/src/dev/impl/DevToys/Models/ValidationBase.cs @@ -0,0 +1,11 @@ +#nullable enable + +namespace DevToys.Models +{ + public record ValidationBase + { + internal bool IsValid { get; set; } + + internal string? ErrorMessage { get; set; } + } +} diff --git a/src/dev/impl/DevToys/Strings/en-US/JwtDecoderEncoder.resw b/src/dev/impl/DevToys/Strings/en-US/JwtDecoderEncoder.resw index 678c56cb68..8ac98bf455 100644 --- a/src/dev/impl/DevToys/Strings/en-US/JwtDecoderEncoder.resw +++ b/src/dev/impl/DevToys/Strings/en-US/JwtDecoderEncoder.resw @@ -118,27 +118,144 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - JWT Decoder tool + JWT Encoder / Decoder tool - JWT Decoder + JWT - + Header - Jwt Token + Token - + Payload Decode a JWT header, payload and signature - - JWT Decoder - + + Algorithm + + + Decode + + + Validate Token + + + No + + + Yes + + + Token has expirations + + + Expire in month(s) + + + Expire in year(s) + + + Token hashing algorithm + + + Settings + + + Encode + + + Encode / Decode + + + Configuration + + + Token has default time + + + Expire in day(s) + + + Expire in hour(s) + + + Expire in minute(s) + + + Private Key + + + Signature + + + Signature Verified + + + Invalid Public Key + + + Invalid Signature + + + Validate actor + + + Validate audience + + + Validate issuer + + + Validate lifetime + + + Valid audiences + + + Valid issuers + + + Select which token paramters to validate + + + Token validation settings + + + Valid audiences are empty + + + Valid issuers are empty + + + The public key provided is a private key + + + Token has audience + + + Token has issuer + + + Select token parameters + + + Invalid Private Key + + + Public Key + + + JWT Encoder / Decoder + + + Invalid Signature + \ No newline at end of file diff --git a/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs b/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs index 4dbcaff78c..d87c9cdf17 100644 --- a/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs +++ b/src/dev/impl/DevToys/UI/Controls/CustomTextBox.xaml.cs @@ -31,7 +31,7 @@ public sealed partial class CustomTextBox : UserControl, ICustomTextBox nameof(Header), typeof(object), typeof(CustomTextBox), - new PropertyMetadata(null)); + new PropertyMetadata(null, OnHeaderPropertyChangedCalled)); public string? Header { @@ -811,6 +811,11 @@ private static void OnIsRichTextEditPropertyChangedCalled(DependencyObject sende ((CustomTextBox)sender).UpdateUI(); } + private static void OnHeaderPropertyChangedCalled(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs) + { + ((CustomTextBox)sender).UpdateUI(); + } + private static void OnTextPropertyChangedCalled(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs) { var customTextBox = (CustomTextBox)sender; diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/Base64TextEncoderDecoder/Base64EncoderDecoderToolProvider.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/Base64TextEncoderDecoder/Base64EncoderDecoderToolProvider.cs index 8f8d7bc8f4..8fc18cab3a 100644 --- a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/Base64TextEncoderDecoder/Base64EncoderDecoderToolProvider.cs +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/Base64TextEncoderDecoder/Base64EncoderDecoderToolProvider.cs @@ -6,6 +6,7 @@ using System.Text.RegularExpressions; using DevToys.Api.Tools; using DevToys.Core.Threading; +using DevToys.Helpers; using DevToys.Shared.Api.Core; using Windows.UI.Xaml.Controls; @@ -46,7 +47,7 @@ public bool CanBeTreatedByTool(string data) } string? trimmedData = data.Trim(); - bool isBase64 = IsBase64DataStrict(trimmedData); + bool isBase64 = Base64Helper.IsBase64DataStrict(trimmedData); return isBase64; } @@ -55,68 +56,5 @@ public IToolViewModel CreateTool() { return _mefProvider.Import(); } - - private bool IsBase64DataStrict(string data) - { - if (string.IsNullOrWhiteSpace(data)) - { - return false; - } - - if (data.Length % 4 != 0) - { - return false; - } - - if (new Regex(@"[^A-Z0-9+/=]", RegexOptions.IgnoreCase).IsMatch(data)) - { - return false; - } - - int equalIndex = data.IndexOf('='); - int length = data.Length; - - if (!(equalIndex == -1 || equalIndex == length - 1 || (equalIndex == length - 2 && data[length - 1] == '='))) - { - return false; - } - - string? decoded; - - try - { - byte[]? decodedData = Convert.FromBase64String(data); - decoded = Encoding.UTF8.GetString(decodedData); - } - catch (Exception) - { - return false; - } - - //check for special chars that you know should not be there - char current; - for (int i = 0; i < decoded.Length; i++) - { - current = decoded[i]; - if (current == 65533) - { - return false; - } - -#pragma warning disable IDE0078 // Use pattern matching - if (!(current == 0x9 - || current == 0xA - || current == 0xD - || (current >= 0x20 && current <= 0xD7FF) - || (current >= 0xE000 && current <= 0xFFFD) - || (current >= 0x10000 && current <= 0x10FFFF))) -#pragma warning restore IDE0078 // Use pattern matching - { - return false; - } - } - - return true; - } } } diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoder.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoder.cs new file mode 100644 index 0000000000..58a2fe33ff --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoder.cs @@ -0,0 +1,301 @@ +#nullable enable + +using System; +using System.Composition; +using System.IdentityModel.Tokens.Jwt; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using DevToys.Helpers; +using DevToys.Helpers.JsonYaml; +using DevToys.Models; +using DevToys.Models.JwtDecoderEncoder; +using DevToys.Shared.Core; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using Microsoft.UI.Xaml.Controls; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.OpenSsl; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + [Export(typeof(JwtDecoder))] + [Shared] + public class JwtDecoder + { + private const string PublicKeyStart = "-----BEGIN PUBLIC KEY-----"; + private const string PublicKeyEnd = "-----END PUBLIC KEY-----"; + private Action? _decodingErrorCallBack; + private JwtDecoderEncoderStrings _localizedStrings => LanguageManager.Instance.JwtDecoderEncoder; + + public TokenResult? DecodeToken( + DecoderParameters decodeParameters, + TokenParameters tokenParameters, + Action decodingErrorCallBack) + { + Arguments.NotNull(decodeParameters, nameof(decodeParameters)); + Arguments.NotNull(tokenParameters, nameof(tokenParameters)); + _decodingErrorCallBack = Arguments.NotNull(decodingErrorCallBack, nameof(decodingErrorCallBack)); + Arguments.NotNullOrWhiteSpace(tokenParameters.Token, nameof(tokenParameters.Token)); + + var tokenResult = new TokenResult(); + + try + { + IdentityModelEventSource.ShowPII = true; + var handler = new JwtSecurityTokenHandler(); + JwtSecurityToken jwtSecurityToken = handler.ReadJwtToken(tokenParameters.Token); + tokenResult.Header = JsonHelper.Format(jwtSecurityToken.Header.SerializeToJson(), Indentation.TwoSpaces, false); + tokenResult.Payload = JsonHelper.Format(jwtSecurityToken.Payload.SerializeToJson(), Indentation.TwoSpaces, false); + tokenResult.TokenAlgorithm = tokenParameters.TokenAlgorithm; + + if (decodeParameters.ValidateSignature) + { + bool signatureValid = ValidateTokenSignature(handler, decodeParameters, tokenParameters, tokenResult); + if (!signatureValid) + { + return null; + } + } + } + catch (Exception exception) + { + RaiseError(exception.Message); + return null; + } + + return tokenResult; + } + + /// + /// Validate the token using the Signing Credentials + /// + private bool ValidateTokenSignature( + JwtSecurityTokenHandler handler, + DecoderParameters decodeParameters, + TokenParameters tokenParameters, + TokenResult tokenResult) + { + SigningCredentials? signingCredentials = GetValidationCredentials(tokenParameters); + var validationParameters = new TokenValidationParameters + { + ValidateIssuerSigningKey = true, + IssuerSigningKey = signingCredentials.Key, + TryAllIssuerSigningKeys = true, + ValidateActor = decodeParameters.ValidateActor, + ValidateLifetime = decodeParameters.ValidateLifetime, + ValidateIssuer = decodeParameters.ValidateIssuer, + ValidateAudience = decodeParameters.ValidateAudience + }; + + /// check if the token issuers are part of the user provided issuers + if (decodeParameters.ValidateIssuer) + { + if (tokenParameters.ValidIssuers.Count == 0) + { + RaiseError(_localizedStrings.ValidIssuersError); + return false; + } + validationParameters.ValidIssuers = tokenParameters.ValidIssuers; + } + + /// check if the token audience are part of the user provided audiences + if (decodeParameters.ValidateAudience) + { + if (tokenParameters.ValidAudiences.Count == 0) + { + RaiseError(_localizedStrings.ValidAudiencesError); + return false; + } + validationParameters.ValidAudiences = tokenParameters.ValidAudiences; + } + + try + { + handler.ValidateToken(tokenParameters.Token, validationParameters, out _); + tokenResult.Signature = tokenParameters.Signature; + tokenResult.PublicKey = tokenParameters.PublicKey; + return true; + } + catch (Exception exception) + { + RaiseError(exception.Message); + } + return false; + } + + /// + /// Get the Signing Credentials depending on the token Algorithm + /// + private SigningCredentials GetValidationCredentials( + TokenParameters tokenParameters) + { + SigningCredentials? signingCredentials = tokenParameters.TokenAlgorithm switch + { + JwtAlgorithm.ES512 => new SigningCredentials(GetECDsaValidationKey(tokenParameters), SecurityAlgorithms.EcdsaSha512Signature), + JwtAlgorithm.ES384 => new SigningCredentials(GetECDsaValidationKey(tokenParameters), SecurityAlgorithms.EcdsaSha384Signature), + JwtAlgorithm.ES256 => new SigningCredentials(GetECDsaValidationKey(tokenParameters), SecurityAlgorithms.EcdsaSha256Signature), + JwtAlgorithm.PS512 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha512), + JwtAlgorithm.PS384 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha384), + JwtAlgorithm.PS256 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha256), + JwtAlgorithm.RS512 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSha512Signature), + JwtAlgorithm.RS384 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSha384Signature), + JwtAlgorithm.RS256 => new SigningCredentials(GetRsaShaValidationKey(tokenParameters), SecurityAlgorithms.RsaSha256Signature), + JwtAlgorithm.HS512 => new SigningCredentials(GetHmacShaValidationKey(tokenParameters), SecurityAlgorithms.HmacSha512Signature), + JwtAlgorithm.HS384 => new SigningCredentials(GetHmacShaValidationKey(tokenParameters), SecurityAlgorithms.HmacSha384Signature), + _ => new SigningCredentials(GetHmacShaValidationKey(tokenParameters), SecurityAlgorithms.HmacSha256Signature),// HS256 + }; + return signingCredentials; + } + + /// + /// Generate a Symetric Security Key using the token signature (base 64 or not) + /// + /// Token parameters with the token signature + private SymmetricSecurityKey? GetHmacShaValidationKey(TokenParameters tokenParameters) + { + if (string.IsNullOrWhiteSpace(tokenParameters.Signature)) + { + return null; + } + + byte[]? signatureByte; + if (Base64Helper.IsBase64DataStrict(tokenParameters.Signature)) + { + signatureByte = Convert.FromBase64String(tokenParameters.Signature); + } + else + { + signatureByte = Encoding.UTF8.GetBytes(tokenParameters.Signature); + } + byte[] byteKey = tokenParameters.TokenAlgorithm switch + { + JwtAlgorithm.HS512 => new HMACSHA512(signatureByte).Key, + JwtAlgorithm.HS384 => new HMACSHA384(signatureByte).Key, + _ => new HMACSHA256(signatureByte).Key,// HS256 + }; + return new SymmetricSecurityKey(byteKey); + } + + /// + /// Generate a RSA Security Key using the token signing public key + /// + /// Token parameters with the token signing public key + private RsaSecurityKey? GetRsaShaValidationKey(TokenParameters tokenParameters) + { + try + { + AsymmetricKeyParameter? asymmetricKeyParameter = GetPublicAsymmetricKeyParameter(tokenParameters); + if (asymmetricKeyParameter is null) + { + RaiseError(_localizedStrings.InvalidPublicKeyError); + return null; + } + + var publicKey = (RsaKeyParameters)asymmetricKeyParameter; + if (publicKey.IsPrivate) + { + RaiseError(_localizedStrings.PublicKeyIsPrivateKeyError); + return null; + } + + RSAParameters rsaParameters = new() + { + Modulus = publicKey.Modulus.ToByteArrayUnsigned(), + Exponent = publicKey.Exponent.ToByteArrayUnsigned() + }; + return new RsaSecurityKey(rsaParameters); + } + catch (Exception exception) + { + RaiseError($"{_localizedStrings.InvalidPublicKeyError}: '{exception.Message}'"); + return null; + } + } + + /// + /// Generate a ECDsa Security Key using the token signing public key + /// + /// Token parameters with the token signing public key + private ECDsaSecurityKey? GetECDsaValidationKey(TokenParameters tokenParameters) + { + try + { + AsymmetricKeyParameter? asymmetricKeyParameter = GetPublicAsymmetricKeyParameter(tokenParameters); + if (asymmetricKeyParameter is null) + { + RaiseError(_localizedStrings.InvalidPublicKeyError); + return null; + } + + var publicKey = (ECPublicKeyParameters)asymmetricKeyParameter; + if (publicKey.IsPrivate) + { + RaiseError(_localizedStrings.PublicKeyIsPrivateKeyError); + return null; + } + + ECParameters ecParameters = new() + { + Curve = ECCurve.NamedCurves.nistP256, + Q = new() + { + X = publicKey.Q.AffineXCoord.GetEncoded(), + Y = publicKey.Q.AffineYCoord.GetEncoded() + } + }; + var ecdSa = ECDsa.Create(ecParameters); + return new ECDsaSecurityKey(ecdSa); + } + catch (Exception exception) + { + RaiseError($"{_localizedStrings.InvalidPublicKeyError}: '{exception.Message}'"); + return null; + } + } + + /// + /// Generate the Asymetric Security Key using the token signing public key + /// + /// Token parameters with the token signing public key + private AsymmetricKeyParameter? GetPublicAsymmetricKeyParameter(TokenParameters tokenParameters) + { + if (string.IsNullOrWhiteSpace(tokenParameters.PublicKey)) + { + RaiseError(_localizedStrings.InvalidPublicKeyError); + return null; + } + var publicKeyStringBuilder = new StringBuilder(tokenParameters.PublicKey!.Trim()); + if (!tokenParameters.PublicKey!.StartsWith(PublicKeyStart, StringComparison.OrdinalIgnoreCase)) + { + publicKeyStringBuilder.Insert(0, PublicKeyStart); + } + if (!tokenParameters.PublicKey.EndsWith(PublicKeyEnd, StringComparison.OrdinalIgnoreCase)) + { + publicKeyStringBuilder.Append(PublicKeyEnd); + } + + var pemReader = new PemReader(new StringReader(publicKeyStringBuilder.ToString())); + var asymetricPublicKey = (AsymmetricKeyParameter)pemReader.ReadObject(); + if (asymetricPublicKey is null) + { + RaiseError(_localizedStrings.InvalidPublicKeyError); + return null; + } + + return asymetricPublicKey; + } + + private void RaiseError(string message) + { + var eventArg = new TokenResultErrorEventArgs + { + Message = message, + Severity = InfoBarSeverity.Error + }; + _decodingErrorCallBack!.Invoke(eventArg); + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControlViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControlViewModel.cs new file mode 100644 index 0000000000..5f751d7f9b --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControlViewModel.cs @@ -0,0 +1,119 @@ +#nullable enable + +using System; +using System.Composition; +using System.Linq; +using DevToys.Api.Core; +using DevToys.Api.Core.Settings; +using DevToys.Api.Tools; +using DevToys.Core.Threading; +using DevToys.Models; +using DevToys.Models.JwtDecoderEncoder; +using DevToys.Shared.Core.Threading; +using DevToys.Views.Tools.EncodersDecoders.JwtDecoderEncoder; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + [Export(typeof(JwtDecoderControlViewModel))] + public sealed class JwtDecoderControlViewModel : JwtDecoderEncoderViewModelBase, IToolViewModel, IRecipient + { + private readonly JwtDecoder _decoder; + + public Type View { get; } = typeof(JwtDecoderControl); + + [ImportingConstructor] + public JwtDecoderControlViewModel( + ISettingsProvider settingsProvider, + IMarketingService marketingService, + JwtDecoder decoder) + : base(settingsProvider, marketingService) + { + IsActive = true; + _decoder = decoder; + ShowValidation = ValidateSignature; + } + + public void Receive(JwtJobAddedMessage message) + { + if (string.IsNullOrWhiteSpace(Token)) + { + return; + } + + DecoderParameters decoderParamters = new(); + if (ValidateSignature) + { + decoderParamters.ValidateSignature = ValidateSignature; + decoderParamters.ValidateAudience = ValidateAudience; + decoderParamters.ValidateLifetime = ValidateLifetime; + decoderParamters.ValidateIssuer = ValidateIssuer; + decoderParamters.ValidateActor = ValidateActor; + } + + TokenParameters tokenParameters = new() + { + Token = Token, + Signature = Signature, + PublicKey = PublicKey, + }; + + if (!string.IsNullOrEmpty(ValidIssuers)) + { + tokenParameters.ValidIssuers = ValidIssuers!.Split(',').ToHashSet(); + } + + if (!string.IsNullOrEmpty(ValidAudiences)) + { + tokenParameters.ValidAudiences = ValidAudiences!.Split(',').ToHashSet(); + } + + TokenResult? result = _decoder.DecodeToken(decoderParamters, tokenParameters, TokenErrorCallBack); + + ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => + { + if (result is null) + { + return; + } + + Header = result.Header; + Payload = result.Payload; + + if (ValidateSignature) + { + RequireSignature = true; + if (result.TokenAlgorithm is + not JwtAlgorithm.HS256 and + not JwtAlgorithm.HS384 and + not JwtAlgorithm.HS512) + { + RequireSignature = false; + } + + } + + DisplayValidationInfoBar(); + + + if (ToolSuccessfullyWorked) + { + ToolSuccessfullyWorked = true; + MarketingService.NotifyToolSuccessfullyWorked(); + } + }).ForgetSafely(); + } + + private void TokenErrorCallBack(TokenResultErrorEventArgs e) + { + JwtValidation.IsValid = false; + JwtValidation.ErrorMessage = e.Message; + ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => + { + Header = string.Empty; + Payload = string.Empty; + DisplayValidationInfoBar(); + }).ForgetSafely(); + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderSettings.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderSettings.cs new file mode 100644 index 0000000000..62cb74d763 --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderSettings.cs @@ -0,0 +1,208 @@ +#nullable enable + +using DevToys.Api.Core.Settings; +using DevToys.Models; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + internal static class JwtDecoderEncoderSettings + { + /// + /// Define if we need to show the validation parameters / inputs + /// + public static readonly SettingDefinition ShowValidation + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ShowValidation)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token signature + /// + public static readonly SettingDefinition ValidateSignature + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidateSignature)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token issuer + /// + public static readonly SettingDefinition ValidateIssuer + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidateIssuer)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token actor + /// + public static readonly SettingDefinition ValidateActor + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidateActor)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token audience + /// + public static readonly SettingDefinition ValidateAudience + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidateAudience)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token lifetime + /// + public static readonly SettingDefinition ValidateLifetime + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidateLifetime)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if we need to validate the token lifetime + /// + public static readonly SettingDefinition JWtToolMode + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(JWtToolMode)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if the token has an expiration + /// + public static readonly SettingDefinition HasExpiration + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(HasExpiration)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if the token has a default time + /// + public static readonly SettingDefinition HasDefaultTime + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(HasDefaultTime)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if the token has a default time + /// + public static readonly SettingDefinition HasAudience + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(HasAudience)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if the token has a default time + /// + public static readonly SettingDefinition ValidAudiences + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidAudiences)}", + isRoaming: true, + defaultValue: string.Empty); + + /// + /// Define if the token has a default time + /// + public static readonly SettingDefinition HasIssuer + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(HasIssuer)}", + isRoaming: true, + defaultValue: false); + + /// + /// Define if the token has a default time + /// + public static readonly SettingDefinition ValidIssuers + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ValidIssuers)}", + isRoaming: true, + defaultValue: string.Empty); + + /// + /// Define if the token expiration year + /// + public static readonly SettingDefinition ExpireYear + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ExpireYear)}", + isRoaming: true, + defaultValue: 0); + + /// + /// Define if the token expiration month + /// + public static readonly SettingDefinition ExpireMonth + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ExpireMonth)}", + isRoaming: true, + defaultValue: 0); + + /// + /// Define if the token expiration day + /// + public static readonly SettingDefinition ExpireDay + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ExpireDay)}", + isRoaming: true, + defaultValue: 0); + + /// + /// Define if the token expiration hours + /// + public static readonly SettingDefinition ExpireHour + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ExpireHour)}", + isRoaming: true, + defaultValue: 0); + + /// + /// Define if the token expiration minutes + /// + public static readonly SettingDefinition ExpireMinute + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(ExpireMinute)}", + isRoaming: true, + defaultValue: 0); + + /// + /// Define if the token expiration minutes + /// + public static readonly SettingDefinition JwtAlgorithm + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(JwtAlgorithm)}", + isRoaming: true, + defaultValue: Models.JwtAlgorithm.HS256); + + /// + /// Define if the token expiration minutes + /// + public static readonly SettingDefinition PublicKey + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(PublicKey)}", + isRoaming: true, + defaultValue: string.Empty); + + /// + /// Define if the token expiration minutes + /// + public static readonly SettingDefinition PrivateKey + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(PrivateKey)}", + isRoaming: true, + defaultValue: string.Empty); + + /// + /// Define if the token expiration minutes + /// + public static readonly SettingDefinition Signature + = new( + name: $"{nameof(JwtDecoderEncoderViewModelBase)}.{nameof(Signature)}", + isRoaming: true, + defaultValue: string.Empty); + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolViewModel.cs index 63eb8980b2..0d87d7aa8e 100644 --- a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolViewModel.cs +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolViewModel.cs @@ -1,131 +1,48 @@ #nullable enable using System; -using System.Collections.Generic; using System.Composition; -using System.IdentityModel.Tokens.Jwt; -using System.Threading.Tasks; -using DevToys.Api.Core; +using DevToys.Api.Core.Settings; using DevToys.Api.Tools; -using DevToys.Core.Threading; -using DevToys.Shared.Core.Threading; -using DevToys.Models; +using DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder; using DevToys.Views.Tools.JwtDecoderEncoder; using Microsoft.Toolkit.Mvvm.ComponentModel; -using DevToys.Helpers.JsonYaml; namespace DevToys.ViewModels.Tools.JwtDecoderEncoder { [Export(typeof(JwtDecoderEncoderToolViewModel))] public sealed class JwtDecoderEncoderToolViewModel : ObservableRecipient, IToolViewModel { - private readonly IMarketingService _marketingService; - private readonly Queue _decodingQueue = new(); + private readonly ISettingsProvider _settingsProvider; - private bool _toolSuccessfullyWorked; - private bool _decodingInProgress; - private string? _jwtToken; - private string? _jwtHeader; - private string? _jwtPayload; - - public Type View { get; } = typeof(JwtDecoderEncoderToolPage); - - internal JwtDecoderEncoderStrings Strings => LanguageManager.Instance.JwtDecoderEncoder; - - /// - /// Gets or sets the jwt token. - /// - internal string? JwtToken + internal bool JwtToolMode { - get => _jwtToken; + get => _settingsProvider.GetSetting(JwtDecoderEncoderSettings.JWtToolMode); set { - SetProperty(ref _jwtToken, value?.Trim()); - QueueConversion(); + _settingsProvider.SetSetting(JwtDecoderEncoderSettings.JWtToolMode, value); + OnPropertyChanged(); } } - /// - /// Gets or sets the jwt header. - /// - internal string? JwtHeader - { - get => _jwtHeader; - set => SetProperty(ref _jwtHeader, value); - } - - /// - /// Gets or sets the jwt payload. - /// - internal string? JwtPayload - { - get => _jwtPayload; - set => SetProperty(ref _jwtPayload, value); - } - - [ImportingConstructor] - public JwtDecoderEncoderToolViewModel(IMarketingService marketingService) - { - _marketingService = marketingService; - } - - private void QueueConversion() - { - _decodingQueue.Enqueue(JwtToken ?? string.Empty); - TreatQueueAsync().Forget(); - } - - private async Task TreatQueueAsync() - { - if (_decodingInProgress) - { - return; - } - - _decodingInProgress = true; - - await TaskScheduler.Default; + internal JwtDecoderEncoderStrings Strings => LanguageManager.Instance.JwtDecoderEncoder; - while (_decodingQueue.TryDequeue(out string? text)) - { - JwtDecode(text, out string? header, out string? payload); - ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => - { - JwtHeader = header; - JwtPayload = payload; + public Type View { get; } = typeof(JwtDecoderEncoderToolPage); - if (!_toolSuccessfullyWorked) - { - _toolSuccessfullyWorked = true; - _marketingService.NotifyToolSuccessfullyWorked(); - } - }).ForgetSafely(); - } + public JwtDecoderControlViewModel DecoderViewModel { get; private set; } - _decodingInProgress = false; - } + public JwtEncoderControlViewModel EncoderViewModel { get; private set; } - private void JwtDecode(string input, out string header, out string payload) + [ImportingConstructor] + public JwtDecoderEncoderToolViewModel( + JwtDecoderControlViewModel decoderControlViewModel, + JwtEncoderControlViewModel encoderControlViewModel, + ISettingsProvider settingsProvider) { - if (string.IsNullOrWhiteSpace(input)) - { - header = string.Empty; - payload = string.Empty; - return; - } - - try - { - var handler = new JwtSecurityTokenHandler(); - JwtSecurityToken jwtSecurityToken = handler.ReadJwtToken(input); - header = JsonHelper.Format(jwtSecurityToken.Header.SerializeToJson(), Indentation.TwoSpaces, sortProperties: false); - payload = JsonHelper.Format(jwtSecurityToken.Payload.SerializeToJson(), Indentation.TwoSpaces, sortProperties: false); - } - catch (Exception ex) - { - header = ex.Message; - payload = ex.Message; - } + DecoderViewModel = decoderControlViewModel; + EncoderViewModel = encoderControlViewModel; + _settingsProvider = settingsProvider; + IsActive = true; } } } diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderViewModelBase.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderViewModelBase.cs new file mode 100644 index 0000000000..106c513d3b --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderViewModelBase.cs @@ -0,0 +1,312 @@ +#nullable enable + +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using DevToys.Api.Core; +using DevToys.Api.Core.Settings; +using DevToys.Helpers.JsonYaml; +using DevToys.Models; +using DevToys.UI.Controls; +using Microsoft.Toolkit.Mvvm.ComponentModel; +using Microsoft.Toolkit.Mvvm.Messaging; +using Microsoft.UI.Xaml.Controls; +using Windows.UI.Xaml; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + public abstract class JwtDecoderEncoderViewModelBase : ObservableRecipient + { + private string? _token; + private string? _header; + private string? _payload; + private bool _requireSignature; + private JwtAlgorithmDisplayPair? _algorithmSelected; + private InfoBarData? _validationResult; + + protected bool WorkInProgress; + protected bool ToolSuccessfullyWorked; + protected readonly Queue JobQueue = new(); + protected readonly ISettingsProvider SettingsProvider; + protected readonly IMarketingService MarketingService; + + internal ValidationBase JwtValidation = new(); + + internal JwtDecoderEncoderStrings LocalizedStrings => LanguageManager.Instance.JwtDecoderEncoder; + + internal RoutedEventHandler InputFocusChanged { get; } + + internal virtual string? Token + { + get => _token; + set + { + if (_token != value) + { + SetProperty(ref _token, value?.Trim()); + QueueNewTokenJob(); + } + } + } + + internal string? Header + { + get => _header; + set + { + if (_header != value) + { + SetProperty(ref _header, value); + QueueNewTokenJob(); + } + } + } + + internal string? Payload + { + get => _payload; + set + { + if (_payload != value) + { + SetProperty(ref _payload, value); + QueueNewTokenJob(); + } + } + } + + internal bool ValidateSignature + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidateSignature); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidateSignature, value); + OnPropertyChanged(); + ShowValidation = value; + QueueNewTokenJob(); + } + } + + internal bool ValidateIssuer + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidateIssuer); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidateIssuer, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool ValidateActor + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidateActor); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidateActor, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool ValidateAudience + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidateAudience); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidateAudience, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool ValidateLifetime + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidateLifetime); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidateLifetime, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal string? ValidIssuers + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidIssuers); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidIssuers, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal string? ValidAudiences + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ValidAudiences); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ValidAudiences, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal string? PublicKey + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.PublicKey); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.PublicKey, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal string? PrivateKey + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.PrivateKey); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.PrivateKey, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal string? Signature + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.Signature); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.Signature, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool ShowValidation + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ShowValidation); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ShowValidation, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool RequireSignature + { + get => _requireSignature; + set + { + if (_requireSignature != value) + { + SetProperty(ref _requireSignature, value); + OnPropertyChanged(); + } + } + } + + internal InfoBarData? ValidationResult + { + get => _validationResult; + private set => SetProperty(ref _validationResult, value); + } + + internal JwtAlgorithmDisplayPair AlgorithmMode + { + get + { + JwtAlgorithm settingsValue = SettingsProvider.GetSetting(JwtDecoderEncoderSettings.JwtAlgorithm); + JwtAlgorithmDisplayPair? algorithm = Algorithms.FirstOrDefault(x => x.Value == settingsValue); + Header = JsonHelper.Format(@"{""alg"": """ + algorithm.DisplayName + @""", ""typ"": ""JWT""}", Indentation.TwoSpaces, false); + IsSignatureRequired(algorithm); + return _algorithmSelected ?? JwtAlgorithmDisplayPair.HS256; + } + set + { + if (_algorithmSelected != value) + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.JwtAlgorithm, value.Value); + IsSignatureRequired(value); + SetProperty(ref _algorithmSelected, value); + OnPropertyChanged(); + } + } + } + + internal IReadOnlyList Algorithms = new ObservableCollection { + JwtAlgorithmDisplayPair.HS256, JwtAlgorithmDisplayPair.HS384, JwtAlgorithmDisplayPair.HS512, + JwtAlgorithmDisplayPair.RS256, JwtAlgorithmDisplayPair.RS384, JwtAlgorithmDisplayPair.RS512, + JwtAlgorithmDisplayPair.ES256, JwtAlgorithmDisplayPair.ES384, JwtAlgorithmDisplayPair.ES512, + JwtAlgorithmDisplayPair.PS256, JwtAlgorithmDisplayPair.PS384, JwtAlgorithmDisplayPair.PS512, + }; + + public JwtDecoderEncoderViewModelBase( + ISettingsProvider settingsProvider, + IMarketingService marketingService) + { + SettingsProvider = settingsProvider; + MarketingService = marketingService; + InputFocusChanged = ControlFocusChanged; + IsSignatureRequired(AlgorithmMode); + } + + internal void QueueNewTokenJob() + { + JwtValidation = new ValidationBase + { + IsValid = true + }; + var newJob = new JwtJobAddedMessage(); + Messenger.Send(newJob); + } + + protected void DisplayValidationInfoBar() + { + InfoBarSeverity infoBarSeverity; + string message; + if (!JwtValidation.IsValid) + { + infoBarSeverity = InfoBarSeverity.Error; + message = JwtValidation.ErrorMessage ?? LocalizedStrings.JwtInValidMessage; + } + else + { + infoBarSeverity = InfoBarSeverity.Success; + message = LocalizedStrings.JwtIsValidMessage; + } + + ValidationResult = new InfoBarData(infoBarSeverity, message); + } + + protected void ControlFocusChanged(object source, RoutedEventArgs args) + { + var input = (CustomTextBox)source; + + if (input.Text.Length == 0) + { + return; + } + + QueueNewTokenJob(); + } + + private void IsSignatureRequired(JwtAlgorithmDisplayPair value) + { + if (value.Value is JwtAlgorithm.HS256 || + value.Value is JwtAlgorithm.HS384 || + value.Value is JwtAlgorithm.HS512) + { + RequireSignature = true; + } + else + { + RequireSignature = false; + } + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoder.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoder.cs new file mode 100644 index 0000000000..e225a6a2d0 --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoder.cs @@ -0,0 +1,269 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Composition; +using System.IdentityModel.Tokens.Jwt; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using DevToys.Helpers; +using DevToys.Models; +using DevToys.Models.JwtDecoderEncoder; +using DevToys.Shared.Core; +using Microsoft.IdentityModel.Tokens; +using Microsoft.UI.Xaml.Controls; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.OpenSsl; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + [Export(typeof(JwtEncoder))] + [Shared] + public class JwtEncoder + { + private const string PrivateKeyStart = "-----BEGIN PRIVATE KEY-----"; + private const string PrivateKeyEnd = "-----END PRIVATE KEY-----"; + private Action? _encodingErrorCallBack; + private JwtDecoderEncoderStrings _localizedStrings => LanguageManager.Instance.JwtDecoderEncoder; + + public TokenResult? GenerateToken( + EncoderParameters encodeParameters, + TokenParameters tokenParameters, + Action encodingErrorCallBack) + { + Arguments.NotNull(encodeParameters, nameof(encodeParameters)); + Arguments.NotNull(tokenParameters, nameof(tokenParameters)); + _encodingErrorCallBack = Arguments.NotNull(encodingErrorCallBack, nameof(encodingErrorCallBack)); + Arguments.NotNullOrWhiteSpace(tokenParameters.Payload, nameof(tokenParameters.Payload)); + + var tokenResult = new TokenResult(); + + try + { + var serializeOptions = new JsonSerializerOptions(); + serializeOptions.Converters.Add(new JwtPayloadConverter()); + Dictionary? payload = JsonSerializer.Deserialize>(tokenParameters.Payload!, serializeOptions); + SigningCredentials? signingCredentials = GetSigningCredentials(tokenParameters); + + if (signingCredentials is null) + { + return null; + } + + var tokenDescriptor = new SecurityTokenDescriptor + { + Claims = payload, + SigningCredentials = signingCredentials, + Expires = null + }; + + if (encodeParameters.HasExpiration) + { + DateTime expirationDate = DateTime.UtcNow + .AddYears(tokenParameters.ExpirationYear) + .AddMonths(tokenParameters.ExpirationMonth) + .AddDays(tokenParameters.ExpirationDay) + .AddHours(tokenParameters.ExpirationHour) + .AddMinutes(tokenParameters.ExpirationMinute); + tokenDescriptor.Expires = expirationDate; + } + + if (encodeParameters.HasAudience) + { + tokenDescriptor.Audience = string.Join(',', tokenParameters.ValidAudiences); + } + + if (encodeParameters.HasIssuer) + { + tokenDescriptor.Issuer = string.Join(',', tokenParameters.ValidIssuers); + tokenDescriptor.IssuedAt = DateTime.UtcNow; + } + + var handler = new JwtSecurityTokenHandler + { + SetDefaultTimesOnTokenCreation = false + }; + + if (encodeParameters.HasDefaultTime) + { + handler.SetDefaultTimesOnTokenCreation = true; + tokenDescriptor.Expires = DateTime.UtcNow.AddHours(1); + } + + SecurityToken? token = handler.CreateToken(tokenDescriptor); + tokenResult.Token = handler.WriteToken(token); + tokenResult.Payload = tokenParameters.Payload; + } + catch (Exception exception) + { + RaiseError(exception.Message); + return null; + } + + return tokenResult; + } + + private SigningCredentials GetSigningCredentials(TokenParameters tokenParameters) + { + SigningCredentials? signingCredentials = tokenParameters.TokenAlgorithm switch + { + JwtAlgorithm.ES512 => new SigningCredentials(GetECDsaSigningKey(tokenParameters), SecurityAlgorithms.EcdsaSha512Signature), + JwtAlgorithm.ES384 => new SigningCredentials(GetECDsaSigningKey(tokenParameters), SecurityAlgorithms.EcdsaSha384Signature), + JwtAlgorithm.ES256 => new SigningCredentials(GetECDsaSigningKey(tokenParameters), SecurityAlgorithms.EcdsaSha256Signature), + JwtAlgorithm.PS512 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha512), + JwtAlgorithm.PS384 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha384), + JwtAlgorithm.PS256 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSsaPssSha256), + JwtAlgorithm.RS512 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSha512Signature), + JwtAlgorithm.RS384 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSha384Signature), + JwtAlgorithm.RS256 => new SigningCredentials(GetRsaShaSigningKey(tokenParameters), SecurityAlgorithms.RsaSha256Signature), + JwtAlgorithm.HS512 => new SigningCredentials(GetHmacShaSigningKey(tokenParameters), SecurityAlgorithms.HmacSha512Signature), + JwtAlgorithm.HS384 => new SigningCredentials(GetHmacShaSigningKey(tokenParameters), SecurityAlgorithms.HmacSha384Signature), + _ => new SigningCredentials(GetHmacShaSigningKey(tokenParameters), SecurityAlgorithms.HmacSha256Signature),// HS256 + }; + + return signingCredentials; + } + + private SymmetricSecurityKey? GetHmacShaSigningKey(TokenParameters tokenParameters) + { + if (string.IsNullOrWhiteSpace(tokenParameters.Signature)) + { + throw new InvalidOperationException(_localizedStrings.InvalidSignatureError); + } + + byte[]? signatureByte; + if (Base64Helper.IsBase64DataStrict(tokenParameters.Signature)) + { + signatureByte = Convert.FromBase64String(tokenParameters.Signature); + } + else + { + signatureByte = Encoding.UTF8.GetBytes(tokenParameters.Signature); + } + byte[] byteKey = tokenParameters.TokenAlgorithm switch + { + JwtAlgorithm.HS512 => new HMACSHA512(signatureByte).Key, + JwtAlgorithm.HS384 => new HMACSHA384(signatureByte).Key, + _ => new HMACSHA256(signatureByte).Key,// HS256 + }; + return new SymmetricSecurityKey(byteKey); + } + + private RsaSecurityKey? GetRsaShaSigningKey(TokenParameters tokenParameters) + { + AsymmetricKeyParameter asymmetricKeyParameter = GetPrivateAsymmetricKeyParameter(tokenParameters); + + var rsaPrivateKeyParameters = (RsaPrivateCrtKeyParameters)asymmetricKeyParameter; + if (!rsaPrivateKeyParameters.IsPrivate) + { + throw new InvalidOperationException(_localizedStrings.InvalidPrivateKeyError); + } + + RSAParameters rsaParameters = new(); + rsaParameters.Modulus = rsaPrivateKeyParameters.Modulus.ToByteArrayUnsigned(); + rsaParameters.Exponent = rsaPrivateKeyParameters.PublicExponent.ToByteArrayUnsigned(); + rsaParameters.P = rsaPrivateKeyParameters.P.ToByteArrayUnsigned(); + rsaParameters.Q = rsaPrivateKeyParameters.Q.ToByteArrayUnsigned(); + rsaParameters.D = ConvertRSAParametersField(rsaPrivateKeyParameters.Exponent, rsaParameters.Modulus.Length); + rsaParameters.DP = ConvertRSAParametersField(rsaPrivateKeyParameters.DP, rsaParameters.P.Length); + rsaParameters.DQ = ConvertRSAParametersField(rsaPrivateKeyParameters.DQ, rsaParameters.Q.Length); + rsaParameters.InverseQ = ConvertRSAParametersField(rsaPrivateKeyParameters.QInv, rsaParameters.Q.Length); + + return new RsaSecurityKey(rsaParameters); + } + + private ECDsaSecurityKey? GetECDsaSigningKey(TokenParameters tokenParameters) + { + AsymmetricKeyParameter? asymmetricKeyParameter = GetPrivateAsymmetricKeyParameter(tokenParameters); + var ecPrivateKeyParameters = (ECPrivateKeyParameters)asymmetricKeyParameter!; + if (!ecPrivateKeyParameters.IsPrivate) + { + throw new InvalidOperationException(_localizedStrings.InvalidPrivateKeyError); + } + + ECPoint ecPoint = new() + { + X = ecPrivateKeyParameters.Parameters.G.AffineXCoord.GetEncoded(), + Y = ecPrivateKeyParameters.Parameters.G.AffineYCoord.GetEncoded() + }; + ECParameters ecParameters = new() + { + Curve = ECCurve.NamedCurves.nistP256, + Q = ecPoint, + D = ecPrivateKeyParameters.D.ToByteArrayUnsigned() + }; + + var ecdSa = ECDsa.Create(ecParameters); + return new ECDsaSecurityKey(ecdSa); + } + + private AsymmetricKeyParameter GetPrivateAsymmetricKeyParameter(TokenParameters tokenParameters) + { + if (string.IsNullOrWhiteSpace(tokenParameters.PrivateKey)) + { + throw new InvalidOperationException(_localizedStrings.InvalidPrivateKeyError); + } + var privateKeyStringBuilder = new StringBuilder(tokenParameters.PrivateKey!.Trim()); + if (!tokenParameters.PrivateKey!.StartsWith(PrivateKeyStart, StringComparison.OrdinalIgnoreCase)) + { + privateKeyStringBuilder.Insert(0, PrivateKeyStart); + } + if (!tokenParameters.PrivateKey.EndsWith(PrivateKeyEnd, StringComparison.OrdinalIgnoreCase)) + { + privateKeyStringBuilder.Append(PrivateKeyEnd); + } + + var pemReader = new PemReader(new StringReader(privateKeyStringBuilder.ToString())); + object? pemObject = pemReader.ReadObject(); + if (pemObject is null) + { + throw new InvalidOperationException(_localizedStrings.InvalidPrivateKeyError); + } + + if (pemObject is AsymmetricKeyParameter parameter) + { + return parameter; + } + else if (pemObject is AsymmetricCipherKeyPair) + { + var pair = pemObject as AsymmetricCipherKeyPair; + return pair!.Private; + } + + throw new InvalidOperationException(_localizedStrings.InvalidPrivateKeyError); + } + + /// + /// Source (https://stackoverflow.com/questions/28370414/import-rsa-key-from-bouncycastle-sometimes-throws-bad-data) + /// + private static byte[] ConvertRSAParametersField(BigInteger n, int size) + { + byte[] bs = n.ToByteArrayUnsigned(); + if (bs.Length == size) + { + return bs; + } + if (bs.Length > size) + { + throw new ArgumentException("Specified size too small", "size"); + } + byte[] padded = new byte[size]; + Array.Copy(bs, 0, padded, size - bs.Length, bs.Length); + return padded; + } + + private void RaiseError(string message) + { + var eventArg = new TokenResultErrorEventArgs + { + Message = message, + Severity = InfoBarSeverity.Error + }; + _encodingErrorCallBack!.Invoke(eventArg); + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControlViewModel.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControlViewModel.cs new file mode 100644 index 0000000000..5343a1b3bf --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControlViewModel.cs @@ -0,0 +1,236 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Composition; +using System.Linq; +using DevToys.Api.Core; +using DevToys.Api.Core.Settings; +using DevToys.Api.Tools; +using DevToys.Core.Threading; +using DevToys.Models; +using DevToys.Models.JwtDecoderEncoder; +using DevToys.Shared.Core.Threading; +using DevToys.Views.Tools.EncodersDecoders.JwtDecoderEncoder; +using Microsoft.Toolkit.Mvvm.Messaging; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + [Export(typeof(JwtEncoderControlViewModel))] + public sealed class JwtEncoderControlViewModel : JwtDecoderEncoderViewModelBase, IToolViewModel, IRecipient + { + private string? _token; + private bool _hasError; + + private readonly JwtEncoder _encoder; + + internal override string? Token + { + get => _token; + set + { + if (_token != value) + { + SetProperty(ref _token, value?.Trim()); + } + } + } + + internal bool HasExpiration + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.HasExpiration); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.HasExpiration, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool HasAudience + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.HasAudience); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.HasAudience, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool HasIssuer + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.HasIssuer); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.HasIssuer, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool HasDefaultTime + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.HasDefaultTime); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.HasDefaultTime, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal bool HasError + { + get => _hasError; + set + { + SetProperty(ref _hasError, value); + } + } + + internal int ExpireYear + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ExpireYear); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ExpireYear, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal int ExpireMonth + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ExpireMonth); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ExpireMonth, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal int ExpireDay + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ExpireDay); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ExpireDay, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + + } + + internal int ExpireHour + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ExpireHour); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ExpireHour, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + internal int ExpireMinute + { + get => SettingsProvider.GetSetting(JwtDecoderEncoderSettings.ExpireMinute); + set + { + SettingsProvider.SetSetting(JwtDecoderEncoderSettings.ExpireMinute, value); + OnPropertyChanged(); + QueueNewTokenJob(); + } + } + + public Type View { get; } = typeof(JwtEncoderControl); + + [ImportingConstructor] + public JwtEncoderControlViewModel( + ISettingsProvider settingsProvider, + IMarketingService marketingService, + JwtEncoder encoder) + : base(settingsProvider, marketingService) + { + IsActive = true; + _encoder = encoder; + } + + public void Receive(JwtJobAddedMessage message) + { + if (string.IsNullOrWhiteSpace(Payload)) + { + return; + } + + EncoderParameters encoderParameters = new() + { + HasAudience = HasAudience, + HasExpiration = HasExpiration, + HasIssuer = HasIssuer, + HasDefaultTime = HasDefaultTime + }; + + TokenParameters tokenParameters = new() + { + TokenAlgorithm = AlgorithmMode.Value, + Payload = Payload, + ExpirationYear = ExpireYear, + ExpirationMonth = ExpireMonth, + ExpirationDay = ExpireDay, + ExpirationHour = ExpireHour, + ExpirationMinute = ExpireMinute + }; + + if (!string.IsNullOrEmpty(ValidIssuers)) + { + tokenParameters.ValidIssuers = ValidIssuers!.Split(',').ToHashSet(); + } + + if (!string.IsNullOrEmpty(ValidAudiences)) + { + tokenParameters.ValidAudiences = ValidAudiences!.Split(',').ToHashSet(); + } + + if (AlgorithmMode.Value is JwtAlgorithm.HS256 || + AlgorithmMode.Value is JwtAlgorithm.HS384 || + AlgorithmMode.Value is JwtAlgorithm.HS512) + { + tokenParameters.Signature = Signature; + } + else + { + tokenParameters.PrivateKey = PrivateKey; + } + + TokenResult? result = _encoder.GenerateToken(encoderParameters, tokenParameters, TokenErrorCallBack); + + ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => + { + if (result is not null) + { + Token = result.Token; + } + HasError = JwtValidation.IsValid!; + if (ToolSuccessfullyWorked) + { + ToolSuccessfullyWorked = true; + MarketingService.NotifyToolSuccessfullyWorked(); + } + }).ForgetSafely(); + } + + private void TokenErrorCallBack(TokenResultErrorEventArgs e) + { + JwtValidation.IsValid = false; + JwtValidation.ErrorMessage = e.Message; + ThreadHelper.RunOnUIThreadAsync(ThreadPriority.Low, () => + { + Token = string.Empty; + DisplayValidationInfoBar(); + }).ForgetSafely(); + } + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtJobAddedMessage.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtJobAddedMessage.cs new file mode 100644 index 0000000000..9b593cef77 --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtJobAddedMessage.cs @@ -0,0 +1,8 @@ +#nullable enable + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + public sealed class JwtJobAddedMessage + { + } +} diff --git a/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtPayloadConverter.cs b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtPayloadConverter.cs new file mode 100644 index 0000000000..ac8670bcb6 --- /dev/null +++ b/src/dev/impl/DevToys/ViewModels/Tools/EncodersDecoders/JwtDecoderEncoder/JwtPayloadConverter.cs @@ -0,0 +1,103 @@ +#nullable enable + +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder +{ + internal class JwtPayloadConverter : JsonConverter> + { + public override Dictionary? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var dictionary = new Dictionary(); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string? propertyName = reader.GetString(); + reader.Read(); + object? value = GetValue(ref reader); + + if (!string.IsNullOrWhiteSpace(propertyName) && value is not null) + { + dictionary.Add(propertyName!, value); + } + } + + throw new JsonException(); + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + private object? GetValue(ref Utf8JsonReader reader) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return reader.GetString(); + case JsonTokenType.False: + return false; + case JsonTokenType.True: + return true; + case JsonTokenType.Null: + return null; + case JsonTokenType.Number: + if (reader.TryGetInt64(out long _long)) + { + return _long; + } + else if (reader.TryGetDecimal(out decimal _dec)) + { + return _dec; + } + throw new JsonException($"Unhandled Number value"); + case JsonTokenType.StartArray: + List array = new(); + while (reader.Read() && + reader.TokenType != JsonTokenType.EndArray) + { + array.Add(GetValue(ref reader)); + } + return array.ToArray(); + case JsonTokenType.StartObject: + var result = new ExpandoObject(); + while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) + { + string? propertyName = null; + if (reader.TokenType is JsonTokenType.PropertyName) + { + propertyName = reader.GetString(); + } + reader.Read(); + if (!string.IsNullOrEmpty(propertyName)) + { + result.TryAdd(propertyName, GetValue(ref reader)); + } + } + return result; + default: + return JsonDocument.ParseValue(ref reader).RootElement.Clone(); + } + } + } +} diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml new file mode 100644 index 0000000000..8c8a1a7e59 --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml.cs b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml.cs new file mode 100644 index 0000000000..120862703b --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderControl.xaml.cs @@ -0,0 +1,33 @@ +#nullable enable + +using System.Composition; +using DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace DevToys.Views.Tools.EncodersDecoders.JwtDecoderEncoder +{ + public sealed partial class JwtDecoderControl : UserControl + { + public static readonly DependencyProperty ViewModelProperty + = DependencyProperty.Register( + nameof(ViewModel), + typeof(JwtDecoderControlViewModel), + typeof(JwtDecoderControl), + new PropertyMetadata(default(JwtDecoderControlViewModel))); + + /// + /// Gets the page's view model. + /// + public JwtDecoderControlViewModel ViewModel + { + get => (JwtDecoderControlViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + public JwtDecoderControl() + { + InitializeComponent(); + } + } +} diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml index 37b76b4039..7d7e3671a0 100644 --- a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml @@ -5,10 +5,16 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:controls="using:DevToys.UI.Controls" - xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls" + xmlns:converters="using:DevToys.UI.Converters" + xmlns:jwt="using:DevToys.Views.Tools.EncodersDecoders.JwtDecoderEncoder" mc:Ignorable="d" NavigationCacheMode="Required"> + + + + + @@ -18,46 +24,23 @@ - - - + - - - - - - - - - - - - + + + + + + + + + + + diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml.cs b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml.cs index c3b04f6bdc..9cd0de199b 100644 --- a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml.cs +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtDecoderEncoderToolPage.xaml.cs @@ -46,7 +46,8 @@ protected override void OnNavigatedTo(NavigationEventArgs e) if (!string.IsNullOrWhiteSpace(parameters.ClipBoardContent)) { - ViewModel.JwtToken = parameters.ClipBoardContent; + ViewModel.DecoderViewModel.Token = parameters.ClipBoardContent; + ViewModel.JwtToolMode = false; } base.OnNavigatedTo(e); diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml new file mode 100644 index 0000000000..5cb5f7a71d --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml.cs b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml.cs new file mode 100644 index 0000000000..9509392eb8 --- /dev/null +++ b/src/dev/impl/DevToys/Views/Tools/EncodersDecoders/JwtDecoderEncoder/JwtEncoderControl.xaml.cs @@ -0,0 +1,33 @@ +#nullable enable + +using System.Composition; +using DevToys.ViewModels.Tools.EncodersDecoders.JwtDecoderEncoder; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace DevToys.Views.Tools.EncodersDecoders.JwtDecoderEncoder +{ + public sealed partial class JwtEncoderControl : UserControl + { + public static readonly DependencyProperty ViewModelProperty + = DependencyProperty.Register( + nameof(ViewModel), + typeof(JwtEncoderControlViewModel), + typeof(JwtEncoderControl), + new PropertyMetadata(default(JwtEncoderControlViewModel))); + + /// + /// Gets the page's view model. + /// + public JwtEncoderControlViewModel ViewModel + { + get => (JwtEncoderControlViewModel)GetValue(ViewModelProperty); + set => SetValue(ViewModelProperty, value); + } + + public JwtEncoderControl() + { + InitializeComponent(); + } + } +} diff --git a/src/tests/DevToys.Tests/DevToys.Tests.csproj b/src/tests/DevToys.Tests/DevToys.Tests.csproj index 6fe459e517..77deb8c53e 100644 --- a/src/tests/DevToys.Tests/DevToys.Tests.csproj +++ b/src/tests/DevToys.Tests/DevToys.Tests.csproj @@ -44,6 +44,8 @@ + + @@ -55,9 +57,12 @@ + + + @@ -85,6 +90,21 @@ + + + + + + + + + + + + + + + @@ -108,6 +128,15 @@ DevToys.Shared + + + + + + + + +