Skip to content

Handle large SDT values with SHA256 in JWT tokens #1157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions dotnet/src/dotnetframework/GxClasses/Middleware/GXHttp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1860,21 +1860,29 @@ protected string GetObjectAccessWebToken(String cmpCtx)
return GetSecureSignedToken(cmpCtx, string.Empty, this.context);
}

protected string GetSecureSignedToken(String cmpCtx, object Value, IGxContext context)
protected string GetSecureSignedToken(String cmpCtx, object value, IGxContext context)
{
return GetSecureSignedToken(cmpCtx, Serialize(Value), context);
if (value is IGxJSONSerializable)
return GetSecureSignedHashedToken(cmpCtx, SecureTokenHelper.GetTokenValue(value as IGxJSONSerializable), context);
else
return GetSecureSignedToken(cmpCtx, Serialize(value), context);
}

protected string GetSecureSignedToken(String cmpCtx, GxUserType Value, IGxContext context)
{
return GetSecureSignedToken(cmpCtx, Serialize(Value), context);
return GetSecureSignedHashedToken(cmpCtx, SecureTokenHelper.GetTokenValue(Value), context);
}


protected string GetSecureSignedToken(string cmpCtx, string value, IGxContext context)
{
return WebSecurityHelper.Sign(PgmInstanceId(cmpCtx), string.Empty, value, SecureTokenHelper.SecurityMode.Sign, context);
}
private string GetSecureSignedHashedToken(string cmpCtx, TokenValue tokenValue, IGxContext context)
{
return WebSecurityHelper.Sign(PgmInstanceId(cmpCtx), string.Empty, tokenValue, SecureTokenHelper.SecurityMode.Sign, context);
}

protected bool VerifySecureSignedToken(string cmpCtx, Object value, string pic, string signedToken, IGxContext context)
{
GxUserType SDT = value as GxUserType;
Expand Down
116 changes: 96 additions & 20 deletions dotnet/src/dotnetframework/GxClasses/Security/WebSecurity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public static string Sign(string pgmName, string issuer, string value, SecurityM
{
return SecureTokenHelper.Sign(new WebSecureToken { ProgramName = pgmName, Issuer = issuer, Value = string.IsNullOrEmpty(value) ? string.Empty: StripInvalidChars(value) }, mode, GetSecretKey(context));
}
internal static string Sign(string pgmName, string issuer, TokenValue tokenValue, SecurityMode mode, IGxContext context)
{
return SecureTokenHelper.Sign(new WebSecureToken {
ProgramName = pgmName,
Issuer = issuer,
ValueType = tokenValue.ValueType,
Value = string.IsNullOrEmpty(tokenValue.Value) ? string.Empty : StripInvalidChars(tokenValue.Value) },
mode, GetSecretKey(context));
}

private static string GetSecretKey(IGxContext context)
{
Expand Down Expand Up @@ -88,33 +97,65 @@ public static bool Verify(string pgmName, string issuer, string value, string jw

internal static bool VerifySecureSignedSDTToken(string cmpCtx, IGxCollection value, string signedToken, IGxContext context)
{
WebSecureToken Token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context));
if (Token == null)
WebSecureToken token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context));
if (token == null)
return false;
IGxCollection PayloadObject = (IGxCollection)value.Clone();
PayloadObject.FromJSonString(Token.Value);
return GxUserType.IsEqual(value, PayloadObject);
if (token.ValueType == SecureTokenHelper.ValueTypeHash)
{
return VerifyTokenHash(value.ToJSonString(), token);
}
else
{
IGxCollection PayloadObject = (IGxCollection)value.Clone();
PayloadObject.FromJSonString(token.Value);
return GxUserType.IsEqual(value, PayloadObject);
}
}

internal static bool VerifySecureSignedSDTToken(string cmpCtx, GxUserType value, string signedToken, IGxContext context)
{
WebSecureToken Token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context));
if (Token == null)
WebSecureToken token = SecureTokenHelper.getWebSecureToken(signedToken, GetSecretKey(context));
if (token == null)
return false;
GxUserType PayloadObject = (GxUserType)value.Clone();
PayloadObject.FromJSonString(Token.Value);
return GxUserType.IsEqual(value, PayloadObject);
}
if (token.ValueType == ValueTypeHash)
{
return VerifyTokenHash(value.ToJSonString(), token);
}
else
{
GxUserType PayloadObject = (GxUserType)value.Clone();
PayloadObject.FromJSonString(token.Value);
return GxUserType.IsEqual(value, PayloadObject);
}

}

private static bool VerifyTokenHash(string payloadJsonString, WebSecureToken token)
{
string hash = GetHash(payloadJsonString);
if (hash != token.Value)
{
GXLogging.Error(_log, $"WebSecurity Token Verification error - Hash mismatch '{hash}' <> '{token.Value}'");
GXLogging.Debug(_log, "Payload TokenOriginalValue: " + payloadJsonString);
return false;
}
return true;
}
}
internal class TokenValue
{
internal string Value { get; set; }
internal string ValueType { get; set; }
}

[SecuritySafeCritical]
public static class SecureTokenHelper
{

static readonly IGXLogger _log = GXLoggerFactory.GetLogger(typeof(SecureTokenHelper).FullName);
internal const string ValueTypeHash = "hash";
const int MaxTokenValueLength = 1024;

public enum SecurityMode
public enum SecurityMode
{
Sign,
SignEncrypt,
Expand Down Expand Up @@ -144,6 +185,7 @@ internal static WebSecureToken getWebSecureToken(string signedToken, string secr
WebSecureToken outToken = new WebSecureToken();
var claims = handler.ValidateToken(signedToken, validationParameters, out securityToken);
outToken.Value = claims.Identities.First().Claims.First(c => c.Type == WebSecureToken.GXVALUE).Value;
outToken.ValueType = claims.Identities.First().Claims.First(c => c.Type == WebSecureToken.GXVALUE_TYPE)?.Value ?? string.Empty;
return outToken;
}
}
Expand All @@ -161,7 +203,8 @@ public static string Sign(WebSecureToken token, SecurityMode mode, string secret
new Claim(WebSecureToken.GXISSUER, token.Issuer),
new Claim(WebSecureToken.GXPROGRAM, token.ProgramName),
new Claim(WebSecureToken.GXVALUE, token.Value),
new Claim(WebSecureToken.GXEXPIRATION, token.Expiration.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString())
new Claim(WebSecureToken.GXEXPIRATION, token.Expiration.Subtract(new DateTime(1970, 1, 1)).TotalSeconds.ToString()),
new Claim(WebSecureToken.GXVALUE_TYPE, token.ValueType ?? string.Empty)
}),
notBefore: DateTime.UtcNow,
expires: token.Expiration,
Expand Down Expand Up @@ -195,6 +238,7 @@ internal static bool Verify(string jwtToken, WebSecureToken outToken, string sec
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, handler,
new object[] { jwtToken, validationParameters });
Validators.ValidateIssuerSecurityKey(jwtSecurityToken.SigningKey, jwtSecurityToken, validationParameters);

outToken.Expiration = new DateTime(1970, 1, 1).AddSeconds(Double.Parse(jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXEXPIRATION).Value));
outToken.ProgramName = jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXPROGRAM).Value;
outToken.Issuer = jwtSecurityToken.Claims.First(c => c.Type == WebSecureToken.GXISSUER).Value;
Expand All @@ -204,14 +248,42 @@ internal static bool Verify(string jwtToken, WebSecureToken outToken, string sec
}
catch (Exception e)
{
GXLogging.ErrorSanitized(_log, string.Format("Web Token verify failed for Token '{0}'", jwtToken), e);
GXLogging.Error(_log, string.Format("Web Token verify failed for Token '{0}'", jwtToken), e);
}
}
return ok;
}
}
}
internal static TokenValue GetTokenValue(IGxJSONSerializable obj)
{

string jsonString = obj.ToJSonString();

[DataContract]
if (jsonString.Length > MaxTokenValueLength)
{
string hash = GetHash(jsonString);
GXLogging.Debug(_log, $"GetTokenValue: TokenValue is too long, using hash: {hash} instead of original value.");
GXLogging.Debug(_log, $"Server TokenOriginalValue:" + jsonString);
return new TokenValue() { Value = hash, ValueType = ValueTypeHash };
}
else
{
GXLogging.Debug(_log, $"GetTokenValue:" + jsonString);
return new TokenValue() { Value = jsonString };
}
}
internal static string GetHash(string jsonString)
{
using (var sha256 = System.Security.Cryptography.SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(jsonString));
jsonString = Convert.ToBase64String(hashBytes);
return jsonString;
}
}

}

[DataContract]
public abstract class SecureToken : IGxJSONSerializable
{
public abstract string ToJSonString();
Expand Down Expand Up @@ -241,8 +313,9 @@ public class WebSecureToken: SecureToken
internal const string GXPROGRAM = "gx-pgm";
internal const string GXVALUE = "gx-val";
internal const string GXEXPIRATION = "gx-exp";
internal const string GXVALUE_TYPE = "gx-val-type";

[DataMember(Name = GXISSUER, IsRequired = true, EmitDefaultValue = false)]
[DataMember(Name = GXISSUER, IsRequired = true, EmitDefaultValue = false)]
public string Issuer { get; set; }

[DataMember(Name = GXPROGRAM, IsRequired = true, EmitDefaultValue = false)]
Expand All @@ -254,7 +327,9 @@ public class WebSecureToken: SecureToken
[DataMember(Name = GXEXPIRATION, EmitDefaultValue = false)]
public DateTime Expiration { get; set; }

public WebSecureToken()
[DataMember(Name = GXVALUE_TYPE, EmitDefaultValue = false)]
public string ValueType { get; set; }
public WebSecureToken()
{
Expiration = DateTime.Now.AddDays(15);
}
Expand All @@ -267,6 +342,7 @@ public override bool FromJSonString(string s)
this.Value = wt.Value;
this.ProgramName = wt.ProgramName;
this.Issuer = wt.Issuer;
this.ValueType = wt.ValueType;
return true;
}
catch (Exception)
Expand Down
Loading