diff --git a/src/JsonWebToken/Base64.cs b/src/JsonWebToken/Base64.cs
index 7a35f423..a5d4d42f 100644
--- a/src/JsonWebToken/Base64.cs
+++ b/src/JsonWebToken/Base64.cs
@@ -3,7 +3,6 @@
using System;
using System.Buffers;
-using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace JsonWebToken
@@ -12,12 +11,12 @@ namespace JsonWebToken
public static class Base64
{
/// Decodes a span of UTF-8 base64-encoded text.
- /// This method allocate an array of bytes. Use when possible.
- public static byte[] Decode(ReadOnlySpan base64)
+ /// This method allocate an array of bytes. Use when possible.
+ public static byte[] Decode(ReadOnlySpan base64, bool stripWhitespace = false)
{
var dataLength = GetArraySizeRequiredToDecode(base64.Length);
var data = new byte[dataLength];
- int length = Decode(base64, data);
+ int length = Decode(base64, data, stripWhitespace);
if (length != dataLength)
{
data = data.AsSpan(0, length).ToArray();
@@ -29,29 +28,29 @@ public static byte[] Decode(ReadOnlySpan base64)
#if NETSTANDARD2_0
/// Decodes a string of UTF-8 base64-encoded text into a span of bytes.
/// The number of the bytes written to .
- public static int Decode(string base64, Span data)
+ public static int Decode(string base64, Span data, bool stripWhitespace = false)
{
if (base64 is null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.base64url);
}
- return Decode(base64.AsSpan(), data);
+ return Decode(base64.AsSpan(), data, stripWhitespace);
}
#endif
/// Decodes a span of UTF-8 base64-encoded text into a span of bytes.
/// The number of the bytes written to .
- public static int Decode(ReadOnlySpan base64, Span data)
+ public static int Decode(ReadOnlySpan base64, Span data, bool stripWhitespace = false)
{
byte[]? arrayToReturn = null;
var buffer = base64.Length > Constants.MaxStackallocBytes
? (arrayToReturn = ArrayPool.Shared.Rent(base64.Length))
- : stackalloc byte[Constants.MaxStackallocBytes];
+ : stackalloc byte[base64.Length];
try
{
int length = Utf8.GetBytes(base64, buffer);
- return Decode(buffer.Slice(0, length), data);
+ return Decode(buffer.Slice(0, length), data, stripWhitespace);
}
finally
{
@@ -64,9 +63,9 @@ public static int Decode(ReadOnlySpan base64, Span data)
/// Decodes the span of UTF-8 base64-encoded text into a span of bytes.
/// The number of the bytes written to .
- public static int Decode(ReadOnlySpan base64, Span data)
+ public static int Decode(ReadOnlySpan base64, Span data, bool stripWhitespace = false)
{
- var status = Decode(base64, data, out _, out int bytesWritten);
+ var status = Decode(base64, data, out _, out int bytesWritten, stripWhitespace);
if (status != OperationStatus.Done)
{
ThrowHelper.ThrowOperationNotDoneException(status);
@@ -76,68 +75,55 @@ public static int Decode(ReadOnlySpan base64, Span data)
}
/// Decodes the span of UTF-8 base64-encoded text into binary data.
- public static OperationStatus Decode(ReadOnlySpan base64, Span data, out int bytesConsumed, out int bytesWritten)
+ public static OperationStatus Decode(ReadOnlySpan base64, Span data, out int bytesConsumed, out int bytesWritten, bool stripWhitespace = false)
{
- int lastWhitespace = base64.LastIndexOfAny(WhiteSpace);
- if (lastWhitespace == -1)
+ if (stripWhitespace)
{
- return gfoidl.Base64.Base64.Default.Decode(base64, data, out bytesConsumed, out bytesWritten);
- }
- else
- {
- byte[]? utf8ArrayToReturn = null;
- Span utf8Data = base64.Length > Constants.MaxStackallocBytes
- ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(base64.Length))
- : stackalloc byte[Constants.MaxStackallocBytes];
- try
+ int lastWhitespace = base64.LastIndexOfAny(WhiteSpace);
+ if (lastWhitespace != -1)
{
- int firstWhitespace = base64.IndexOfAny(WhiteSpace);
- int length = 0;
- Span buffer = utf8Data;
- if (firstWhitespace != lastWhitespace)
+ byte[]? utf8ArrayToReturn = null;
+ Span utf8Data = base64.Length > Constants.MaxStackallocBytes
+ ? (utf8ArrayToReturn = ArrayPool.Shared.Rent(base64.Length))
+ : stackalloc byte[base64.Length];
+ try
{
- while (firstWhitespace != -1)
+ int length = 0;
+ int i = 0;
+ for (; i <= lastWhitespace; i++)
{
- base64.Slice(0, firstWhitespace).CopyTo(buffer);
- buffer = buffer.Slice(firstWhitespace);
- length += firstWhitespace;
-
- // Skip whitespaces
- int i = firstWhitespace;
- while (++i < base64.Length && IsWhiteSpace(base64[i])) ;
+ var current = base64[i];
+ if (!IsWhiteSpace(current))
+ {
+ utf8Data[length++] = current;
+ }
+ }
- base64 = base64.Slice(i);
- firstWhitespace = base64.IndexOfAny(WhiteSpace);
+ for (; i < base64.Length; i++)
+ {
+ utf8Data[length++] = base64[i];
}
- //// Copy the remaining
- base64.CopyTo(buffer);
- length += base64.Length;
+ return gfoidl.Base64.Base64.Default.Decode(utf8Data.Slice(0, length), data, out bytesConsumed, out bytesWritten);
}
- else
+ finally
{
- base64.Slice(0, firstWhitespace).CopyTo(buffer);
- base64.Slice(firstWhitespace + 1).CopyTo(buffer.Slice(firstWhitespace));
- length = base64.Length - 1;
- }
-
- return gfoidl.Base64.Base64.Default.Decode(utf8Data.Slice(0, length), data, out bytesConsumed, out bytesWritten);
- }
- finally
- {
- if (utf8ArrayToReturn != null)
- {
- ArrayPool.Shared.Return(utf8ArrayToReturn);
+ if (utf8ArrayToReturn != null)
+ {
+ ArrayPool.Shared.Return(utf8ArrayToReturn);
+ }
}
}
}
+
+ return gfoidl.Base64.Base64.Default.Decode(base64, data, out bytesConsumed, out bytesWritten);
}
private static bool IsWhiteSpace(byte c)
=> c == ' ' || (c >= '\t' && c <= '\r');
private static ReadOnlySpan WhiteSpace
- => new byte[] { (byte)' ', (byte)'\t', (byte)'\n', (byte)'\v', (byte)'\f', (byte)'\r' };
+ => new byte[] { (byte)' ', (byte)'\t', (byte)'\r', (byte)'\n', (byte)'\v', (byte)'\f' };
/// Encodes a span of UTF-8 text into a span of bytes.
/// The number of the bytes written to .
@@ -189,7 +175,7 @@ public static byte[] Encode(ReadOnlySpan data)
int length = Utf8.GetMaxByteCount(data.Length);
var utf8Data = length > Constants.MaxStackallocBytes
? (utf8ArrayToReturn = ArrayPool.Shared.Rent(length))
- : stackalloc byte[Constants.MaxStackallocBytes];
+ : stackalloc byte[length];
int written = Utf8.GetBytes(data, utf8Data);
return Encode(utf8Data.Slice(0, written));
@@ -240,36 +226,33 @@ internal static unsafe bool IsBase64String(ReadOnlySpan value)
static bool IsValidBase64Char(char value)
{
- bool result = false;
- if (value <= byte.MaxValue)
+ if (value > byte.MaxValue)
{
- byte byteValue = (byte)value;
+ return false;
+ }
- // 0-9
- if (byteValue >= (byte)'0' && byteValue <= (byte)'9')
- {
- result = true;
- }
- else
- {
- // a-z or A-Z
- byte letter = (byte)(byteValue | 0x20);
- if (letter >= (byte)'a' && letter <= (byte)'z')
- {
- result = true;
- }
- else
- {
- // + or / or whitespaces
- if (byteValue == (byte)'+' || byteValue == (byte)'/' || IsWhiteSpace(byteValue))
- {
- result = true;
- }
- }
- }
+ byte byteValue = (byte)value;
+
+ // 0-9
+ if (byteValue >= (byte)'0' && byteValue <= (byte)'9')
+ {
+ return true;
+ }
+
+ // + or /
+ if (byteValue == (byte)'+' || byteValue == (byte)'/')
+ {
+ return true;
+ }
+
+ // a-z or A-Z
+ byteValue |= 0x20;
+ if (byteValue >= (byte)'a' && byteValue <= (byte)'z')
+ {
+ return true;
}
- return result;
+ return false;
}
}
}
diff --git a/src/JsonWebToken/Cryptography/PemParser.cs b/src/JsonWebToken/Cryptography/PemParser.cs
index 3537cd52..37a63064 100644
--- a/src/JsonWebToken/Cryptography/PemParser.cs
+++ b/src/JsonWebToken/Cryptography/PemParser.cs
@@ -4,6 +4,9 @@ namespace JsonWebToken.Cryptography
{
internal static class PemParser
{
+ internal static ReadOnlySpan BeginPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ' };
+ internal static ReadOnlySpan EndSuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ' };
+
public static AsymmetricJwk Read(string key)
{
if (key is null)
@@ -12,29 +15,46 @@ public static AsymmetricJwk Read(string key)
}
var data = key.AsSpan().Trim();
- if (data.StartsWith(Pkcs8.PrivateKeyPrefix, StringComparison.Ordinal) && data.EndsWith(Pkcs8.PrivateKeySuffix, StringComparison.Ordinal))
- {
- return Pkcs8.ReadPrivateKey(data);
- }
- else if (data.StartsWith(Pkcs8.PublicKeyPrefix, StringComparison.Ordinal) && data.EndsWith(Pkcs8.PublicKeySuffix, StringComparison.Ordinal))
- {
- return Pkcs8.ReadPublicKey(data);
- }
- if (data.StartsWith(Pkcs1.PrivateRsaKeyPrefix, StringComparison.Ordinal) && data.EndsWith(Pkcs1.PrivatRsaKeySuffix, StringComparison.Ordinal))
- {
- return Pkcs1.ReadRsaPrivateKey(data);
- }
- else if (data.StartsWith(Pkcs1.PublicRsaKeyPrefix, StringComparison.Ordinal) && data.EndsWith(Pkcs1.PublicRsaKeySuffix, StringComparison.Ordinal))
+ int startOffset = data.IndexOf(BeginPrefix);
+ int endOffset = data.IndexOf(EndSuffix);
+
+ if (startOffset != -1 && endOffset != -1)
{
- return Pkcs1.ReadRsaPublicKey(data);
- }
+ if (data.Slice(startOffset + BeginPrefix.Length, Pkcs8.PrivateKeyPrefix.Length).SequenceEqual(Pkcs8.PrivateKeyPrefix) &&
+ data.Slice(endOffset + EndSuffix.Length, Pkcs8.PrivateKeySuffix.Length).SequenceEqual(Pkcs8.PrivateKeySuffix))
+ {
+ return Pkcs8.ReadPrivateKey(data.Slice(startOffset + BeginPrefix.Length + Pkcs8.PrivateKeyPrefix.Length, endOffset - startOffset - Pkcs8.PrivateKeySuffix.Length - BeginPrefix.Length));
+ }
+ else if (data.Slice(startOffset + BeginPrefix.Length, Pkcs8.PublicKeyPrefix.Length).SequenceEqual(Pkcs8.PublicKeyPrefix) &&
+ data.Slice(endOffset + EndSuffix.Length, Pkcs8.PublicKeySuffix.Length).SequenceEqual(Pkcs8.PublicKeySuffix))
+ {
+ return Pkcs8.ReadPublicKey(data.Slice(startOffset + BeginPrefix.Length + Pkcs8.PublicKeyPrefix.Length, endOffset - startOffset - Pkcs8.PublicKeySuffix.Length - BeginPrefix.Length));
+ }
+ else if (data.Slice(startOffset + BeginPrefix.Length, Pkcs1.PrivateRsaKeyLabel.Length).SequenceEqual(Pkcs1.PrivateRsaKeyLabel) &&
+ data.Slice(endOffset + EndSuffix.Length, Pkcs1.PrivateRsaKeyLabel.Length).SequenceEqual(Pkcs1.PrivateRsaKeyLabel))
+ {
+ return Pkcs1.ReadRsaPrivateKey(data.Slice(startOffset + BeginPrefix.Length + Pkcs1.PrivateRsaKeyLabel.Length, endOffset - startOffset - Pkcs1.PrivateRsaKeyLabel.Length - BeginPrefix.Length));
+ }
+ else if (data.Slice(startOffset + BeginPrefix.Length, Pkcs1.PublicRsaKeyLabel.Length).SequenceEqual(Pkcs1.PublicRsaKeyLabel) &&
+ data.Slice(endOffset + EndSuffix.Length, Pkcs1.PublicRsaKeyLabel.Length).SequenceEqual(Pkcs1.PublicRsaKeyLabel))
+ {
+ return Pkcs1.ReadRsaPublicKey(data.Slice(startOffset + BeginPrefix.Length + Pkcs1.PublicRsaKeyLabel.Length, endOffset - startOffset - Pkcs1.PublicRsaKeyLabel.Length - BeginPrefix.Length));
+ }
#if SUPPORT_ELLIPTIC_CURVE
- if (data.StartsWith(Pkcs1.PrivateECKeyPrefix, StringComparison.Ordinal) && data.EndsWith(Pkcs1.PrivateECKeySuffix, StringComparison.Ordinal))
- {
- return Pkcs1.ReadECPrivateKey(data);
- }
+ else if (data.Slice(startOffset + BeginPrefix.Length, Pkcs1.PrivateECKeyLabel.Length).SequenceEqual(Pkcs1.PrivateECKeyLabel) &&
+ data.Slice(endOffset + EndSuffix.Length, Pkcs1.PrivateECKeyLabel.Length).SequenceEqual(Pkcs1.PrivateECKeyLabel))
+ {
+ return Pkcs1.ReadECPrivateKey(data.Slice(startOffset + BeginPrefix.Length + Pkcs1.PrivateECKeyLabel.Length, endOffset - startOffset - Pkcs1.PrivateECKeyLabel.Length - BeginPrefix.Length));
+ }
#endif
- throw new ArgumentException("PEM-encoded key be contained within valid prefix and suffix.", nameof(key));
+ else
+ {
+ throw new ArgumentException($"PEM-encoded key of type {data.Slice(endOffset + EndSuffix.Length, data.Length - endOffset - EndSuffix.Length - 5).ToString()} is not supported.", nameof(key));
+ }
+ }
+
+
+ throw new ArgumentException("PEM-encoded key must be contained within valid prefix and suffix.", nameof(key));
}
}
-}
+}
\ No newline at end of file
diff --git a/src/JsonWebToken/Cryptography/Pkcs1.cs b/src/JsonWebToken/Cryptography/Pkcs1.cs
index 2346384d..2fa1c2c4 100644
--- a/src/JsonWebToken/Cryptography/Pkcs1.cs
+++ b/src/JsonWebToken/Cryptography/Pkcs1.cs
@@ -5,14 +5,11 @@ namespace JsonWebToken.Cryptography
{
internal static class Pkcs1
{
- public static ReadOnlySpan PublicRsaKeyPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'R', 'S', 'A', ' ', 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- public static ReadOnlySpan PublicRsaKeySuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ', 'R', 'S', 'A', ' ', 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- public static ReadOnlySpan PrivateRsaKeyPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'R', 'S', 'A', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- public static ReadOnlySpan PrivatRsaKeySuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ', 'R', 'S', 'A', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ public static ReadOnlySpan PublicRsaKeyLabel => new[] { 'R', 'S', 'A', ' ', 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ public static ReadOnlySpan PrivateRsaKeyLabel => new[] { 'R', 'S', 'A', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
#if SUPPORT_ELLIPTIC_CURVE
- public static ReadOnlySpan PrivateECKeyPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'E', 'C', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- public static ReadOnlySpan PrivateECKeySuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ', 'E', 'C', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ public static ReadOnlySpan PrivateECKeyLabel => new[] { 'E', 'C', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
#endif
// SEQUENCE
@@ -20,12 +17,11 @@ internal static class Pkcs1
// INTEGER E
public static RsaJwk ReadRsaPublicKey(ReadOnlySpan key)
{
- var data = key.Slice(PublicRsaKeyPrefix.Length, key.Length - PublicRsaKeyPrefix.Length - PublicRsaKeySuffix.Length);
byte[] tmpArray;
- Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(data.Length));
+ Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(key.Length));
try
{
- int length = Base64.Decode(data, keyData);
+ int length = Base64.Decode(key, keyData, stripWhitespace: true);
var reader = new AsnReader(keyData.Slice(0, length));
reader = reader.ReadSequence();
@@ -58,12 +54,11 @@ public static RsaJwk ReadRsaPublicKey(ReadOnlySpan key)
// INTEGER QI
public static RsaJwk ReadRsaPrivateKey(ReadOnlySpan key)
{
- var data = key.Slice(PrivateRsaKeyPrefix.Length, key.Length - PrivateRsaKeyPrefix.Length - PrivatRsaKeySuffix.Length);
byte[] tmpArray;
- Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(data.Length));
+ Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(key.Length));
try
{
- int length = Base64.Decode(data, keyData);
+ int length = Base64.Decode(key, keyData, stripWhitespace: true);
var reader = new AsnReader(keyData.Slice(0, length));
reader = reader.ReadSequence();
var version = reader.ReadInteger();
@@ -111,12 +106,11 @@ public static RsaJwk ReadRsaPrivateKey(ReadOnlySpan key)
// BIT STRING public key
public static ECJwk ReadECPrivateKey(ReadOnlySpan key)
{
- var data = key.Slice(PrivateECKeyPrefix.Length, key.Length - PrivateECKeyPrefix.Length - PrivateECKeySuffix.Length);
byte[] tmpArray;
- Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(data.Length));
+ Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(key.Length));
try
{
- int length = Base64.Decode(data, keyData);
+ int length = Base64.Decode(key, keyData, stripWhitespace: true);
var reader = new AsnReader(keyData.Slice(0, length));
reader = reader.ReadSequence();
diff --git a/src/JsonWebToken/Cryptography/Pkcs8.cs b/src/JsonWebToken/Cryptography/Pkcs8.cs
index fc8dcade..8e9d706d 100644
--- a/src/JsonWebToken/Cryptography/Pkcs8.cs
+++ b/src/JsonWebToken/Cryptography/Pkcs8.cs
@@ -5,19 +5,18 @@ namespace JsonWebToken.Cryptography
{
internal static class Pkcs8
{
- internal static ReadOnlySpan PublicKeyPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- internal static ReadOnlySpan PublicKeySuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ', 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- internal static ReadOnlySpan PrivateKeyPrefix => new[] { '-', '-', '-', '-', '-', 'B', 'E', 'G', 'I', 'N', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
- internal static ReadOnlySpan PrivateKeySuffix => new[] { '-', '-', '-', '-', '-', 'E', 'N', 'D', ' ', 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ internal static ReadOnlySpan PublicKeyPrefix => new[] { 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ internal static ReadOnlySpan PublicKeySuffix => new[] { 'P', 'U', 'B', 'L', 'I', 'C', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ internal static ReadOnlySpan PrivateKeyPrefix => new[] { 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
+ internal static ReadOnlySpan PrivateKeySuffix => new[] { 'P', 'R', 'I', 'V', 'A', 'T', 'E', ' ', 'K', 'E', 'Y', '-', '-', '-', '-', '-' };
public static AsymmetricJwk ReadPublicKey(ReadOnlySpan key)
{
- var data = key.Slice(PublicKeyPrefix.Length, key.Length - PublicKeyPrefix.Length - PublicKeySuffix.Length);
byte[] tmpArray;
- Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(data.Length));
+ Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(key.Length));
try
{
- int length = Base64.Decode(data, keyData);
+ int length = Base64.Decode(key, keyData, stripWhitespace: true);
var reader = new AsnReader(keyData.Slice(0, length));
reader = reader.ReadSequence();
var readerOid = reader.ReadSequence();
@@ -48,12 +47,11 @@ public static AsymmetricJwk ReadPublicKey(ReadOnlySpan key)
// NULL or OBJECT IDENTIFIER (EC curve OID)
public static AsymmetricJwk ReadPrivateKey(ReadOnlySpan key)
{
- var data = key.Slice(PrivateKeyPrefix.Length, key.Length - PrivateKeyPrefix.Length - PrivateKeySuffix.Length);
byte[] tmpArray;
- Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(data.Length));
+ Span keyData = tmpArray = ArrayPool.Shared.Rent(Base64.GetArraySizeRequiredToDecode(key.Length));
try
{
- int length = Base64.Decode(data, keyData);
+ int length = Base64.Decode(key, keyData, stripWhitespace: true);
var reader = new AsnReader(keyData.Slice(0, length));
reader = reader.ReadSequence();
reader.ReadInteger();
@@ -346,4 +344,4 @@ public static bool IsRsaKeyOid(int[] oid)
oid[6] == 1;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/JsonWebToken/Jwk.cs b/src/JsonWebToken/Jwk.cs
index c8689ec9..18fe8da2 100644
--- a/src/JsonWebToken/Jwk.cs
+++ b/src/JsonWebToken/Jwk.cs
@@ -892,7 +892,7 @@ public static void Validate(string json)
keyLength = Base64Url.GetArraySizeRequiredToDecode(n.GetString()!.Length) * 8;
}
- if (keyLength % 256 != 0 || keyLength < 512)
+ if ((keyLength & 255) != 0 || keyLength < 512)
{
throw new JwkValidationException(@$"Invalid key length. Must be a multiple of 256 bits, and at least 512 bits. Current key length is {keyLength}.");
}
@@ -900,11 +900,12 @@ public static void Validate(string json)
CheckRequiredBase64UrlMember(document, JwkParameterNames.E);
int privateRsaMembers = CheckOptionalBase64UrlMember(document, JwkParameterNames.D, keyLength);
- privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.P, keyLength / 2) << 1;
- privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.Q, keyLength / 2) << 2;
- privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.DP, keyLength / 2) << 3;
- privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.DQ, keyLength / 2) << 4;
- privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.QI, keyLength / 2) << 5;
+ int halfKeyLength = keyLength / 2;
+ privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.P, halfKeyLength) << 1;
+ privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.Q, halfKeyLength) << 2;
+ privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.DP, halfKeyLength) << 3;
+ privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.DQ, halfKeyLength) << 4;
+ privateRsaMembers |= CheckOptionalBase64UrlMember(document, JwkParameterNames.QI, halfKeyLength) << 5;
if (privateRsaMembers != 0 && privateRsaMembers != (1 | 2 | 4 | 8 | 16 | 32))
{
if ((privateRsaMembers & 1) == 0)
@@ -1144,7 +1145,6 @@ static bool TryCheckRequiredBase64UrlMember(JsonDocument document, JsonEncodedTe
return false;
}
-
static int CheckOptionalBase64UrlMember(JsonDocument document, JsonEncodedText memberName, int length)
{
if (document.RootElement.TryGetProperty(memberName.EncodedUtf8Bytes, out var value))
diff --git a/src/JsonWebToken/JwkKeyOpsValues.cs b/src/JsonWebToken/JwkKeyOpsValues.cs
index cbca02b5..54cb23a8 100644
--- a/src/JsonWebToken/JwkKeyOpsValues.cs
+++ b/src/JsonWebToken/JwkKeyOpsValues.cs
@@ -10,7 +10,6 @@ namespace JsonWebToken
/// http://tools.ietf.org/html/rfc7517#section-4
///
public static class JwkKeyOpsValues
-
{
/// Gets the 'sign' (compute digital signature or MAC) value for the 'key_ops' parameter.
public static readonly JsonEncodedText Sign = JsonEncodedText.Encode("sign");
diff --git a/test/JsonWebToken.Tests/Base64Tests.cs b/test/JsonWebToken.Tests/Base64Tests.cs
index 012c96f3..22362917 100644
--- a/test/JsonWebToken.Tests/Base64Tests.cs
+++ b/test/JsonWebToken.Tests/Base64Tests.cs
@@ -6,6 +6,19 @@ namespace JsonWebToken.Tests
{
public class Base64Tests
{
+ [Theory]
+ [InlineData("", "")]
+ [InlineData("SGVsbG8=", "Hello")]
+ [InlineData("SGVsbG8gV29ybGQ=", "Hello World")]
+ [InlineData("SGV+bG8=", "He~lo")]
+ [InlineData("SGV/bG8=", "He\u007flo")]
+ public void Decode_Valid(string value, string expected)
+ {
+ var result = Base64.Decode(Encoding.UTF8.GetBytes(value));
+ Assert.NotNull(result);
+ Assert.Equal(Encoding.UTF8.GetBytes(expected), result);
+ }
+
[Theory]
[InlineData("", "")]
[InlineData("SGVsbG8=", "Hello")]
@@ -23,9 +36,9 @@ public class Base64Tests
[InlineData(" S G V s b G8gV29ybGQ= ", "Hello World")]
[InlineData("SGV+bG8=", "He~lo")]
[InlineData("SGV/bG8=", "He\u007flo")]
- public void Decode_Valid(string value, string expected)
+ public void Decode_WithWithspaceStripping_Valid(string value, string expected)
{
- var result = Base64.Decode(Encoding.UTF8.GetBytes(value));
+ var result = Base64.Decode(Encoding.UTF8.GetBytes(value), true);
Assert.NotNull(result);
Assert.Equal(Encoding.UTF8.GetBytes(expected), result);
}
@@ -40,5 +53,22 @@ public void Decode_Invalid(string value)
{
Assert.Throws(() => Base64.Decode(Encoding.UTF8.GetBytes(value)));
}
+
+ [Theory]
+ [InlineData("SGVsbG8\tgV29ybGQ=")]
+ [InlineData("SGVsbG8\rgV29ybGQ=")]
+ [InlineData("SGVsbG8\ngV29ybGQ=")]
+ [InlineData("SGVsbG8\vgV29ybGQ=")]
+ [InlineData("SGVsbG8\fgV29ybGQ=")]
+ [InlineData("SGVsbG8 gV29ybGQ=")]
+ [InlineData(" SGVsbG8gV29ybGQ=")]
+ [InlineData("SG Vsb G8gV29ybGQ=")]
+ [InlineData("S G V s b G 8 g V 2 9 y b G Q =")]
+ [InlineData("S G V s b G8gV29ybGQ=")]
+ [InlineData(" S G V s b G8gV29ybGQ= ")]
+ public void Decode_WithoutWithstripping_Invalid(string value)
+ {
+ Assert.Throws(() => Base64.Decode(Encoding.UTF8.GetBytes(value), false));
+ }
}
}
diff --git a/test/JsonWebToken.Tests/ECJwkTests.cs b/test/JsonWebToken.Tests/ECJwkTests.cs
index b8adbbf2..43f72678 100644
--- a/test/JsonWebToken.Tests/ECJwkTests.cs
+++ b/test/JsonWebToken.Tests/ECJwkTests.cs
@@ -362,10 +362,38 @@ public override void WriteTo()
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgQHs5HRkpurXDPaabivT2IaRoyYt
Isuk92Ner/JmgKjYoSumHVmSNfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ==
-----END PUBLIC KEY-----";
+ private const string Pkcs8PemECPrivateKeyExplanatoryText = @"
+Subject: CN=Atlantis
+Issuer: CN=Atlantis
+Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC
+-----BEGIN PRIVATE KEY-----
+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgcKEsLbFoRe1W/2jP
+whpHKz8E19aFG/Y0ny19WzRSs4qhRANCAASBAezkdGSm6tcM9ppuK9PYhpGjJi0i
+y6T3Y16v8maAqNihK6YdWZI19n2ctNWPF4PTykPnjwpauqYkB5k2wMOp
+-----END PRIVATE KEY-----this was a key.";
+ private const string Pkcs1PemECPrivateKeyExplanatoryText = @"
+Subject: CN=Atlantis
+Issuer: CN=Atlantis
+Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC
+-----BEGIN EC PRIVATE KEY-----
+MHcCAQEEIHChLC2xaEXtVv9oz8IaRys/BNfWhRv2NJ8tfVs0UrOKoAoGCCqGSM49
+AwEHoUQDQgAEgQHs5HRkpurXDPaabivT2IaRoyYtIsuk92Ner/JmgKjYoSumHVmS
+NfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ==
+-----END EC PRIVATE KEY-----this was a key.";
+ private const string Pkcs8PemECPublicKeyExplanatoryText = @"
+Subject: CN=Atlantis
+Issuer: CN=Atlantis
+Validity: from 7/9/2012 3:10:38 AM UTC to 7/9/2013 3:10:37 AM UTC
+-----BEGIN PUBLIC KEY-----
+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEgQHs5HRkpurXDPaabivT2IaRoyYt
+Isuk92Ner/JmgKjYoSumHVmSNfZ9nLTVjxeD08pD548KWrqmJAeZNsDDqQ==
+-----END PUBLIC KEY-----this was a key.";
[Theory]
[InlineData(Pkcs1PemECPrivateKey)]
[InlineData(Pkcs8PemECPrivateKey)]
+ [InlineData(Pkcs1PemECPrivateKeyExplanatoryText)]
+ [InlineData(Pkcs8PemECPrivateKeyExplanatoryText)]
public void FromPem_PrivateKey(string pem)
{
var key = ECJwk.FromPem(pem);
@@ -377,6 +405,7 @@ public void FromPem_PrivateKey(string pem)
[Theory]
[InlineData(Pkcs8PemECPublicKey)]
+ [InlineData(Pkcs8PemECPublicKeyExplanatoryText)]
public void FromPem_PublicKey(string pem)
{
var key = ECJwk.FromPem(pem);
diff --git a/test/JsonWebToken.Tests/JsonWebKeyTests.cs b/test/JsonWebToken.Tests/JsonWebKeyTests.cs
index 92f32f4b..bb181525 100644
--- a/test/JsonWebToken.Tests/JsonWebKeyTests.cs
+++ b/test/JsonWebToken.Tests/JsonWebKeyTests.cs
@@ -156,6 +156,37 @@ public void CreateFromCertificate_Error()
Assert.Null(key);
}
+ [Theory]
+ [InlineData(@"-----BEGIN PRIVATE KEY-----
+MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAtz9Z9e6L1V4kt/8C
+mtFqhUPJbSU+VDGbk1MsQcPBR3uJ2y0vM9e5qHRYSOBqjmg7UERRHhvKNiUn4Xz0
+KzgGFQIDAQABAkEAr+byNi+cr17FpJH4MCEiPXaKnmkH4c4U52EJtL9yg2gijBrp
+Ykat3c2nWb0EGGi5aWgXxQHoi7z97/ACD4X3KQIhAPNyex6GdiBVlNPHOgInTU8a
+mARKKVHIXM0SxvxXrRl7AiEAwLI66OpSqftDTv1KUfNe6+hyoh23ggzUSYiWuVT0
+Ya8CHwiO/cUU9RIt8A2B84gf2ZfuV2nPMaSuZpTPFC/K5UsCIQCsJMzx1JuilQAN
+acPiMCuFTnRSFYAhozpmsqoLyTREqwIhAMLJlZTGjEB2N+sEazH5ToEczQzKqp7t
+9juGNbOPhoEL
+-----END PRIVATE KEY-----", 512, true)]
+ public void CreateFromPem(string pem, int keySize, bool hasPrivateKey)
+ {
+ var jwk = Jwk.FromPem(pem);
+
+ Assert.Equal(keySize, jwk.KeySizeInBits);
+ Assert.Equal(hasPrivateKey, jwk.HasPrivateKey);
+ }
+
+ [Theory]
+ [InlineData(@"-----BEGIN CMS-----
+MIGDBgsqhkiG9w0BCRABCaB0MHICAQAwDQYLKoZIhvcNAQkQAwgwXgYJKoZIhvcN
+AQcBoFEET3icc87PK0nNK9ENqSxItVIoSa0o0S/ISczMs1ZIzkgsKk4tsQ0N1nUM
+dvb05OXi5XLPLEtViMwvLVLwSE0sKlFIVHAqSk3MBkkBAJv0Fx0=
+-----END CMS-----", "CMS")]
+ public void CreateFromPem_Invalid_ThrowsException(string pem, string unkwnowLabel)
+ {
+ var exception = Assert.Throws(() => Jwk.FromPem(pem));
+ Assert.Contains(unkwnowLabel, exception.Message);
+ }
+
public static IEnumerable