Skip to content

Commit

Permalink
Jwt Utils (#803)
Browse files Browse the repository at this point in the history
  • Loading branch information
scottf authored Jul 31, 2023
1 parent 4f2c00a commit a34cc81
Show file tree
Hide file tree
Showing 7 changed files with 1,174 additions and 126 deletions.
364 changes: 364 additions & 0 deletions src/NATS.Client/Internals/Encoding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,364 @@
// Copyright 2015-2018 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// 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.

using System;
using System.Text;

namespace NATS.Client
{
public static class EncodingUtils
{
public static string ToBase64UrlEncoded(byte[] bytes)
{
string s = Convert.ToBase64String(bytes);
int at = s.IndexOf('=');
if (at != -1)
{
s = s.Substring(0, at);
}
return s.Replace("/", "_").Replace("+", "-");
}

public static string FromBase64UrlEncoded(string s)
{
try
{
return Encoding.ASCII.GetString(Convert.FromBase64String(s));
}
catch (System.FormatException)
{
// maybe wasn't padded correctly?
try
{
return Encoding.ASCII.GetString(Convert.FromBase64String(s + "="));
}
catch (System.FormatException)
{
// maybe wasn't padded correctly?
return Encoding.ASCII.GetString(Convert.FromBase64String(s + "=="));
}
}
}
}

public class Base32
{
public static byte[] Decode(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ArgumentNullException("input");
}

input = input.TrimEnd('='); //remove padding characters
int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
byte[] returnArray = new byte[byteCount];

byte curByte = 0, bitsRemaining = 8;
int mask = 0, arrayIndex = 0;

foreach (char c in input)
{
int cValue = CharToValue(c);

if (bitsRemaining > 5)
{
mask = cValue << (bitsRemaining - 5);
curByte = (byte)(curByte | mask);
bitsRemaining -= 5;
}
else
{
mask = cValue >> (5 - bitsRemaining);
curByte = (byte)(curByte | mask);
returnArray[arrayIndex++] = curByte;
curByte = (byte)(cValue << (3 + bitsRemaining));
bitsRemaining += 3;
}
}

//if we didn't end with a full byte
if (arrayIndex != byteCount)
{
returnArray[arrayIndex] = curByte;
}

return returnArray;
}

public static string Encode(byte[] input)
{
if (input == null || input.Length == 0)
{
throw new ArgumentNullException("input");
}

int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
char[] returnArray = new char[charCount];

byte nextChar = 0, bitsRemaining = 5;
int arrayIndex = 0;

foreach (byte b in input)
{
nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
returnArray[arrayIndex++] = ValueToChar(nextChar);

if (bitsRemaining < 4)
{
nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
returnArray[arrayIndex++] = ValueToChar(nextChar);
bitsRemaining += 5;
}

bitsRemaining -= 3;
nextChar = (byte)((b << bitsRemaining) & 31);
}

// if we didn't end with a full char
if (arrayIndex < charCount)
{
returnArray[arrayIndex++] = ValueToChar(nextChar);
// NOTE: Base32 padding omitted
}

return new string(returnArray, 0, arrayIndex);
}

private static int CharToValue(char c)
{
int value = (int)c;

//65-90 == uppercase letters
if (value < 91 && value > 64) return value - 65;

//50-55 == numbers 2-7
if (value < 56 && value > 49) return value - 24;

//97-122 == lowercase letters
if (value < 123 && value > 96) return value - 97;

throw new ArgumentException("Character is not a Base32 character.", "c");
}

private static char ValueToChar(byte b)
{
if (b < 26) return (char)(b + 65);
if (b < 32) return (char)(b + 24);
throw new ArgumentException("Byte is not a value Base32 value.", "b");
}

// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------
// ----------------------------------------------------------------------------------------------------

/// <summary>
/// Size of the regular byte in bits
/// </summary>
private const int InByteSize = 8;

/// <summary>
/// Size of converted byte in bits
/// </summary>
private const int OutByteSize = 5;

/// <summary>
/// Alphabet
/// </summary>
private const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

/// <summary>
/// Convert byte array to Base32 format
/// </summary>
/// <param name="bytes">An array of bytes to convert to Base32 format</param>
/// <returns>Returns a string representing byte array</returns>
public static string ToBase32String(byte[] bytes)
{
// Check if byte array is null
if (bytes == null)
{
return null;
}
// Check if empty
else if (bytes.Length == 0)
{
return string.Empty;
}

// Prepare container for the final value
StringBuilder builder = new StringBuilder(bytes.Length * InByteSize / OutByteSize);

// Position in the input buffer
int bytesPosition = 0;

// Offset inside a single byte that <bytesPosition> points to (from left to right)
// 0 - highest bit, 7 - lowest bit
int bytesSubPosition = 0;

// Byte to look up in the dictionary
byte outputBase32Byte = 0;

// The number of bits filled in the current output byte
int outputBase32BytePosition = 0;

// Iterate through input buffer until we reach past the end of it
while (bytesPosition < bytes.Length)
{
// Calculate the number of bits we can extract out of current input byte to fill missing bits in the output byte
int bitsAvailableInByte = Math.Min(InByteSize - bytesSubPosition, OutByteSize - outputBase32BytePosition);

// Make space in the output byte
outputBase32Byte <<= bitsAvailableInByte;

// Extract the part of the input byte and move it to the output byte
outputBase32Byte |= (byte)(bytes[bytesPosition] >> (InByteSize - (bytesSubPosition + bitsAvailableInByte)));

// Update current sub-byte position
bytesSubPosition += bitsAvailableInByte;

// Check overflow
if (bytesSubPosition >= InByteSize)
{
// Move to the next byte
bytesPosition++;
bytesSubPosition = 0;
}

// Update current base32 byte completion
outputBase32BytePosition += bitsAvailableInByte;

// Check overflow or end of input array
if (outputBase32BytePosition >= OutByteSize)
{
// Drop the overflow bits
outputBase32Byte &= 0x1F; // 0x1F = 00011111 in binary

// Add current Base32 byte and convert it to character
builder.Append(Base32Alphabet[outputBase32Byte]);

// Move to the next byte
outputBase32BytePosition = 0;
}
}

// Check if we have a remainder
if (outputBase32BytePosition > 0)
{
// Move to the right bits
outputBase32Byte <<= (OutByteSize - outputBase32BytePosition);

// Drop the overflow bits
outputBase32Byte &= 0x1F; // 0x1F = 00011111 in binary

// Add current Base32 byte and convert it to character
builder.Append(Base32Alphabet[outputBase32Byte]);
}

return builder.ToString();
}

/// <summary>
/// Convert base32 string to array of bytes
/// </summary>
/// <param name="base32String">Base32 string to convert</param>
/// <returns>Returns a byte array converted from the string</returns>
public static byte[] FromBase32String(string base32String)
{
// Check if string is null
if (base32String == null)
{
return null;
}
// Check if empty
else if (base32String == string.Empty)
{
return new byte[0];
}

// Convert to upper-case
string base32StringUpperCase = base32String.ToUpperInvariant();

// Prepare output byte array
byte[] outputBytes = new byte[base32StringUpperCase.Length * OutByteSize / InByteSize];

// Check the size
if (outputBytes.Length == 0)
{
throw new ArgumentException("Specified string is not valid Base32 format because it doesn''t have enough data to construct a complete byte array");
}

// Position in the string
int base32Position = 0;

// Offset inside the character in the string
int base32SubPosition = 0;

// Position within outputBytes array
int outputBytePosition = 0;

// The number of bits filled in the current output byte
int outputByteSubPosition = 0;

// Normally we would iterate on the input array but in this case we actually iterate on the output array
// We do it because output array doesn''t have overflow bits, while input does and it will cause output array overflow if we don''t stop in time
while (outputBytePosition < outputBytes.Length)
{
// Look up current character in the dictionary to convert it to byte
int currentBase32Byte = Base32Alphabet.IndexOf(base32StringUpperCase[base32Position]);

// Check if found
if (currentBase32Byte < 0)
{
throw new ArgumentException(string.Format("Specified string is not valid Base32 format because character \"{0}\" does not exist in Base32 alphabet", base32String[base32Position]));
}

// Calculate the number of bits we can extract out of current input character to fill missing bits in the output byte
int bitsAvailableInByte = Math.Min(OutByteSize - base32SubPosition, InByteSize - outputByteSubPosition);

// Make space in the output byte
outputBytes[outputBytePosition] <<= bitsAvailableInByte;

// Extract the part of the input character and move it to the output byte
outputBytes[outputBytePosition] |= (byte)(currentBase32Byte >> (OutByteSize - (base32SubPosition + bitsAvailableInByte)));

// Update current sub-byte position
outputByteSubPosition += bitsAvailableInByte;

// Check overflow
if (outputByteSubPosition >= InByteSize)
{
// Move to the next byte
outputBytePosition++;
outputByteSubPosition = 0;
}

// Update current base32 byte completion
base32SubPosition += bitsAvailableInByte;

// Check overflow or end of input array
if (base32SubPosition >= OutByteSize)
{
// Move to the next character
base32Position++;
base32SubPosition = 0;
}
}

return outputBytes;
}
}
}
Loading

0 comments on commit a34cc81

Please sign in to comment.