//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. // All rights reserved. // // This code is licensed under the MIT License. // // 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. // //------------------------------------------------------------------------------ using System; using System.Text; using Microsoft.IdentityModel.Logging; namespace Microsoft.IdentityModel.Tokens { /// /// Encodes and Decodes strings as Base64Url encoding. /// public static class Base64UrlEncoder { private const char base64PadCharacter = '='; #if NET45 private const string doubleBase64PadCharacter = "=="; #endif private const char base64Character62 = '+'; private const char base64Character63 = '/'; private const char base64UrlCharacter62 = '-'; private const char base64UrlCharacter63 = '_'; /// /// Encoding table /// internal static readonly char[] s_base64Table = { 'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z', 'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z', '0','1','2','3','4','5','6','7','8','9', base64UrlCharacter62, base64UrlCharacter63 }; /// /// The following functions perform base64url encoding which differs from regular base64 encoding as follows /// * padding is skipped so the pad character '=' doesn't have to be percent encoded /// * the 62nd and 63rd regular base64 encoding characters ('+' and '/') are replace with ('-' and '_') /// The changes make the encoding alphabet file and URL safe. /// /// string to encode. /// Base64Url encoding of the UTF8 bytes. public static string Encode(string arg) { _ = arg ?? throw LogHelper.LogArgumentNullException(nameof(arg)); return Encode(Encoding.UTF8.GetBytes(arg)); } /// /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation which is encoded with base-64-url digits. Parameters specify /// the subset as an offset in the input array, and the number of elements in the array to convert. /// /// An array of 8-bit unsigned integers. /// An offset in inArray. /// The number of elements of inArray to convert. /// The string representation in base 64 url encoding of length elements of inArray, starting at position offset. /// 'inArray' is null. /// offset or length is negative OR offset plus length is greater than the length of inArray. public static string Encode(byte[] inArray, int offset, int length) { _ = inArray ?? throw LogHelper.LogArgumentNullException(nameof(inArray)); if (length == 0) return string.Empty; if (length < 0) throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(length)), LogHelper.MarkAsNonPII(length)))); if (offset < 0 || inArray.Length < offset) throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(offset)), LogHelper.MarkAsNonPII(offset)))); if (inArray.Length < offset + length) throw LogHelper.LogExceptionMessage(new ArgumentOutOfRangeException(LogHelper.FormatInvariant(LogMessages.IDX10106, LogHelper.MarkAsNonPII(nameof(length)), LogHelper.MarkAsNonPII(length)))); int lengthmod3 = length % 3; int limit = offset + (length - lengthmod3); char[] output = new char[(length + 2) / 3 * 4]; char[] table = s_base64Table; int i, j = 0; // takes 3 bytes from inArray and insert 4 bytes into output for (i = offset; i < limit; i += 3) { byte d0 = inArray[i]; byte d1 = inArray[i + 1]; byte d2 = inArray[i + 2]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)]; output[j + 2] = table[((d1 & 0x0f) << 2) | (d2 >> 6)]; output[j + 3] = table[d2 & 0x3f]; j += 4; } //Where we left off before i = limit; switch (lengthmod3) { case 2: { byte d0 = inArray[i]; byte d1 = inArray[i + 1]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[((d0 & 0x03) << 4) | (d1 >> 4)]; output[j + 2] = table[(d1 & 0x0f) << 2]; j += 3; } break; case 1: { byte d0 = inArray[i]; output[j + 0] = table[d0 >> 2]; output[j + 1] = table[(d0 & 0x03) << 4]; j += 2; } break; //default or case 0: no further operations are needed. } return new string(output, 0, j); } /// /// Converts a subset of an array of 8-bit unsigned integers to its equivalent string representation which is encoded with base-64-url digits. /// /// An array of 8-bit unsigned integers. /// The string representation in base 64 url encoding of length elements of inArray, starting at position offset. /// 'inArray' is null. /// offset or length is negative OR offset plus length is greater than the length of inArray. public static string Encode(byte[] inArray) { _ = inArray ?? throw LogHelper.LogArgumentNullException(nameof(inArray)); return Encode(inArray, 0, inArray.Length); } internal static string EncodeString(string str) { _ = str ?? throw LogHelper.LogArgumentNullException(nameof(str)); return Encode(Encoding.UTF8.GetBytes(str)); } /// /// Converts the specified string, which encodes binary data as base-64-url digits, to an equivalent 8-bit unsigned integer array. /// base64Url encoded string. /// UTF8 bytes. public static byte[] DecodeBytes(string str) { _ = str ?? throw LogHelper.LogExceptionMessage(new ArgumentNullException(nameof(str))); #if NET45 // 62nd char of encoding str = str.Replace(base64UrlCharacter62, base64Character62); // 63rd char of encoding str = str.Replace(base64UrlCharacter63, base64Character63); // check for padding switch (str.Length % 4) { case 0: // No pad chars in this case break; case 2: // Two pad chars str += doubleBase64PadCharacter; break; case 3: // One pad char str += base64PadCharacter; break; default: throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, str))); } return Convert.FromBase64String(str); #else return UnsafeDecode(str); #endif } #if !NET45 private unsafe static byte[] UnsafeDecode(string str) { int mod = str.Length % 4; if (mod == 1) throw LogHelper.LogExceptionMessage(new FormatException(LogHelper.FormatInvariant(LogMessages.IDX10400, str))); bool needReplace = false; int decodedLength = str.Length + (4 - mod) % 4; for (int i = 0; i < str.Length; i++) { if (str[i] == base64UrlCharacter62 || str[i] == base64UrlCharacter63) { needReplace = true; break; } } if (needReplace) { string decodedString = new string(char.MinValue, decodedLength); fixed (char* dest = decodedString) { int i = 0; for (; i < str.Length; i++) { if (str[i] == base64UrlCharacter62) dest[i] = base64Character62; else if (str[i] == base64UrlCharacter63) dest[i] = base64Character63; else dest[i] = str[i]; } for (; i < decodedLength; i++) dest[i] = base64PadCharacter; } return Convert.FromBase64String(decodedString); } else { if (decodedLength == str.Length) { return Convert.FromBase64String(str); } else { string decodedString = new string(char.MinValue, decodedLength); fixed (char* src = str) fixed (char* dest = decodedString) { Buffer.MemoryCopy(src, dest, str.Length * 2, str.Length * 2); dest[str.Length] = base64PadCharacter; if (str.Length + 2 == decodedLength) dest[str.Length + 1] = base64PadCharacter; } return Convert.FromBase64String(decodedString); } } } #endif /// /// Decodes the string from Base64UrlEncoded to UTF8. /// /// string to decode. /// UTF8 string. public static string Decode(string arg) { return Encoding.UTF8.GetString(DecodeBytes(arg)); } } }