0

I'm attempting to implement encrypted JSON properties using code from the question Using Json.NET, how can I encrypt selected properties of any type when serializing my objects?. I had originally set up a few "keys" as constants (Const) in my application, but I recently had a thought of using another, more unique value to apply this encryption - a token that the user will be required to enter during the initial setup of the application. However, when I tried to change to using that token instead of my original keys, I'm encountering the Constant expression is required message.

The original, working code for flagging the JSON properties for encryption looks like this:

<EditorBrowsable(EditorBrowsableState.Never)>
Private Class MyEndpoint
    <JsonProperty("endpoint")> <JsonEncrypt(EndpointKey)>
    Public Property Endpoint As Endpoint
End Class

<EditorBrowsable(EditorBrowsableState.Never)>
Private Class MyConnectionString
    <JsonProperty("connection_string")> <JsonEncrypt(ConnectionStringKey)>
    Public Property ConnectionString As ConnectionString
End Class

<EditorBrowsable(EditorBrowsableState.Never)>
Private Class MyUserCredential
    <JsonProperty("credentials")> <JsonEncrypt(CredentialKey)>
    Public Property Credential As Credential
End Class

I have the JsonEncryptAttribute class like this:

<AttributeUsage(AttributeTargets.[Property] Or AttributeTargets.Field, AllowMultiple:=False)>
Public NotInheritable Class JsonEncryptAttribute
    Inherits Attribute

    Public Property EncryptionKey As Byte()

    Public Sub New()
        Me.EncryptionKey = GenerateKey(DefaultKey)
    End Sub

    Public Sub New(ByVal Password As String)
        Me.EncryptionKey = GenerateKey(Password)
    End Sub

    Private Function GenerateKey(ByVal Password As String) As Byte()
        Return SHA256.HashData(Encoding.UTF8.GetBytes(Password))
    End Function
End Class

However, when I change the MyEndpoint class definition to look like this:

<EditorBrowsable(EditorBrowsableState.Never)>
Private Class MyEndpoint
    <JsonProperty("endpoint")> <JsonEncrypt(Settings.AppTokens("myapp"))>
    Public Property Endpoint As Endpoint
End Class

...where AppTokens is a property in the Settings class that will be saved and read separately, the IDE balks and gives me the Constant expression is required message. Based on the constructor for the JsonEncryptAttribute class, I'm completely confused about why it would require the value to be a Const in the first place.

However, looking at the documentation, I figure the error has to do with the fact that the AppTokens (which is defined as a Dictionary(Of String, String)) is the most likely culprit:

A Const statement does not properly initialize a constant, or an array declaration uses a variable to specify the number of elements.

Error ID: BC30059

To correct this error

If the declaration is a Const statement, check to make sure the constant is initialized with a literal, a previously declared constant, an enumeration member, or a combination of literals, constants, and enumeration members combined with operators.

If the declaration specifies an array, check to see if a variable is being used to specify the number of elements. If so, replace the variable with a constant expression.

I looked at the self-accepted answer to another question - Variable in enum attribute : constant expression is required - and tried to implement something similar but, unless I'm just doing it wrong (a definite possibility), it doesn't seem to work with class properties.

<JsonEncrypt(GetType(Settings), NameOf(Settings.AppTokens("myapp")))>

So, now I'm left with the following question(s):

  1. Can anyone explain to me why this error is occurring here? Does it have something to do with the JsonEncryptAttribute's inheritance of the base System.Attribute?

  2. Is there a way to work around this by using a different object type or something? The AppTokens is read from a "base" JSON configuration file for the application, so whatever alternative would have to be compatible with that.

G_Hosa_Phat
  • 976
  • 2
  • 18
  • 38
  • 1
    Attributes are evaluated at compile time so you can't use code that would only be evaluated at run time to specify values to them. You have to provide a value that is known at compile time and that the compiler knows will be know at compile time. – jmcilhinney May 07 '23 at 05:44
  • Thank you, @jmcilhinney. That at least helps me to understand the "*why*" a bit better. It *is* the inheritance of the base `System.Attribute` that's causing the restriction. The compiler doesn't know what I do - that this token will have a value by the time I call it for that particular constructor. It sounds like there isn't much "wiggle room" there, so I most likely will have to drop back to my "pre-defined" keys for this instead. – G_Hosa_Phat May 07 '23 at 13:50

1 Answers1

0

I believe I've found a workable solution where I can use the token as the encryption key. It just requires a small bit of refactoring and restructuring what I had. After reading How to set dynamic value in my Attribute, I took the EndpointKey, ConnectionStringKey, and CredentialKey values I had previously defined as individual Const fields and converted them to an enum:

Public Enum ApplicationKeys
    UserToken
    <Description("Default Encryption Key")> DefaultKey
    <Description("Encrypted Setting Key")> Setting
    <Description("Encrypted ConnectionString Key")> ConnectionString
    <Description("Encrypted Endpoint Key")> Endpoint
    <Description("Encrypted Credential Key")> Credential
End Enum

Then, I set up a new constructor for the JsonEncryptAttribute that accepts this enum as a parameter:

Public Sub New(ByVal Key As ApplicationKeys)
    If Key = ApplicationKeys.UserToken Then
        Me.EncryptionKey = GenerateKey(Settings.AppTokens("myapp"))
    Else
        Me.EncryptionKey = GenerateKey(DirectCast([Enum].Parse(GetType(ApplicationKeys), Key), ApplicationKeys).GetDescription)
    End If
End Sub

This uses the GetDescription extension method† to pull the "pre-defined" key values for anywhere I might want to use them, but also enables me to draw in the dynamic token if that enum is selected.

Obviously, I'll need to put some additional error handling in there for the possibility that the token is Nothing or an empty string, or if the value passed to the constructor isn't defined as a part of the ApplicationKeys enumeration, but I believe this will provide the solution I was really trying to achieve.


† Just to help out anyone else who might be looking at this in the future, here's the GetDescription extension method:

''' <summary>
''' Gets the Description attribute of an Enum value
''' </summary>
''' <param name="CurrentEnum">The specific Enum value for which to get the Description attribute</param>
''' <returns>String value representing the Description attribute of the specified Enum</returns>
''' <remarks>Requires System.Reflection, System.ComponentModel and System.Runtime.CompilerServices namespaces to be imported.
''' The Description attribute of an Enum is set in the Enum declaration.'</remarks>
<DebuggerStepThrough>
<Extension()>
Public Function GetDescription(ByVal CurrentEnum As [Enum]) As String
    Dim DescAttribute As DescriptionAttribute
    Dim Field As FieldInfo = CurrentEnum.GetType.GetField(CurrentEnum.ToString)

    DescAttribute = DirectCast(Attribute.GetCustomAttribute(Field, GetType(DescriptionAttribute)), DescriptionAttribute)

    If DescAttribute IsNot Nothing Then
        Return DescAttribute.Description
    Else
        Return CurrentEnum.ToString
    End If
End Function
G_Hosa_Phat
  • 976
  • 2
  • 18
  • 38