-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
72ae07a
commit d5a41a8
Showing
5 changed files
with
220 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
BuildingBlocks/src/Crypto/Implementations/LibsodiumSymmetricEncrypter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using System.Text; | ||
using System.Text.Json; | ||
using Backbone.Tooling.JsonConverters; | ||
using Sodium; | ||
|
||
// ReSharper disable InconsistentNaming | ||
#pragma warning disable IDE1006 | ||
|
||
namespace Backbone.Crypto.Implementations; | ||
public class LibsodiumSymmetricEncrypter | ||
{ | ||
public static byte[] DecryptXChaCha20Poly1305(byte[] body, string serializedSecret) | ||
{ | ||
var bodyString = Encoding.UTF8.GetString(body); | ||
var deserializedBody = JsonSerializer.Deserialize<CryptoCipher>(bodyString, | ||
new JsonSerializerOptions { Converters = { new UrlSafeBase64ToByteArrayJsonConverter() } }) ?? | ||
throw new InvalidOperationException("Decryption failed."); | ||
|
||
var cipherText = deserializedBody.cph.Replace('-', '+').Replace('_', '/') + "="; | ||
var payload = Convert.FromBase64String(cipherText); | ||
|
||
var nonce = Convert.FromBase64String(deserializedBody.nnc); | ||
|
||
var deserializedSecret = JsonSerializer.Deserialize<Secret>(serializedSecret, | ||
new JsonSerializerOptions { Converters = { new UrlSafeBase64ToByteArrayJsonConverter() } }); | ||
var key = Convert.FromBase64String(deserializedSecret!.key + "="); | ||
|
||
return DecryptXChaCha20Poly1305(payload, nonce, key); | ||
} | ||
|
||
public static byte[] DecryptXChaCha20Poly1305(byte[] payload, byte[] nonce, byte[] key) | ||
{ | ||
if (nonce.Length != 24) | ||
throw new ArgumentException("Nonce must be 24 bytes long for XChaCha20-Poly1305.", nameof(nonce)); | ||
if (key.Length != 32) | ||
throw new ArgumentException("Key must be 32 bytes long for XChaCha20-Poly1305.", nameof(key)); | ||
|
||
try | ||
{ | ||
var decryptedData = SecretAeadXChaCha20Poly1305.Decrypt(payload, nonce, key); | ||
return decryptedData; | ||
} | ||
catch (Exception ex) | ||
{ | ||
throw new InvalidOperationException("Decryption failed.", ex); | ||
} | ||
} | ||
|
||
private class CryptoCipher | ||
{ | ||
/** | ||
* Algorithm (`alg`) and `@Type` fields are omitted due to not being used in the code. | ||
*/ | ||
public required string cph { get; init; } | ||
public required string nnc { get; init; } | ||
} | ||
|
||
private class Secret | ||
{ | ||
/** | ||
* Algorithm (`alg`) field is omitted due to not being used in the code. | ||
*/ | ||
public required string key { get; init; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
140 changes: 140 additions & 0 deletions
140
Modules/Messages/test/Messages.Application.Tests/Tests/AutoMapper/DecryptTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
using Sodium; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using FluentAssertions; | ||
using Xunit; | ||
using Backbone.Tooling.JsonConverters; | ||
using System.Text.Json; | ||
using Backbone.Crypto.Implementations; | ||
using Backbone.Modules.Messages.Domain.Entities; | ||
using Backbone.UnitTestTools.Data; | ||
|
||
namespace Backbone.Modules.Messages.Application.Tests.Tests.AutoMapper; | ||
public class DecryptTest | ||
{ | ||
[Fact] | ||
public void DecryptTest_Test() | ||
{ | ||
var secret = new byte[] { 201, 46, 175, 20, 46, 244, 122, 153, 152, 22, 133, 0, 34, 138, 101, 184, 185, 137, 37, 65, 249, 240, 85, 70, 159, 2, 55, 1, 29, 95, 61, 159 }; | ||
var nonce = new byte[] { 94, 51, 92, 74, 14, 221, 224, 69, 110, 177, 185, 235, 61, 52, 57, 218, 59, 149, 120, 29, 112, 40, 245, 9 }; | ||
var bodyTest = new byte[] | ||
{ | ||
155, 199, 40, 26, 102, 100, 69, 125, 57, 64, 201, 18, 176, 32, 87, 172, 182, 196, 192, 71, 226, 84, 148, 45, 19, 155, 181, 213, 233, 21, 46, 230, 54, 224, 191, 42, 223, 12, 50, 75, 251, | ||
60, 78, 236, 166, 56, 227, 7, 140, 86, 183, 70, 65, 20, 31, 98, 229, 66, 149, 226, 222, 47, 86, 241, 139, 145, 44, 247, 111, 37, 150, 238, 140, 62, 198, 170, 236, 32, 38, 73, 218, 148, 3, | ||
44, 215, 138, 155, 250, 103, 88, 231, 93, 58, 150, 113, 16, 112, 157, 163, 208, 71, 98, 69, 150, 207, 71, 56, 200, 113, 112, 82, 230, 252, 26, 242, 93, 210, 173, 219, 126, 91, 135, 209, | ||
209, 4, 165, 101, 135, 107, 88, 104, 3, 240, 134, 5, 118, 74, 231, 50, 21, 92, 227, 131, 202, 163, 223, 157, 93, 15, 60, 191, 34, 24, 142, 115, 62, 124, 142, 105, 250, 239, 208, 33, 21, | ||
128, 117, 248, 205, 23, 149, 156, 219, 214, 1, 143, 142, 97, 15, 120, 52, 2, 68, 96, 103, 203, 145, 239, 251, 229, 149, 117, 225, 243, 180, 169, 49, 187, 0, 197, 42, 153, 101, 175, 163, | ||
13, 144, 26, 83, 24, 202, 128, 49, 87, 109, 42, 48, 172, 20, 167, 175, 73, 161, 209, 72, 100, 115, 0, 160, 96, 197, 166, 218, 118, 104, 23, 81, 250, 93, 128, 65, 31, 189, 138, 235, 223, | ||
230, 36, 207, 248, 55, 86, 245, 164, 0, 114, 12, 23, 245, 178, 165, 115, 193, 141, 240, 123, 216, 206, 16, 215, 52, 244, 227, 147, 38, 123, 103, 228, 23, 169, 244, 220, 15, 154, 246, 92, | ||
242, 177, 133, 132, 167, 241, 50, 177, 244, 139, 23, 118, 9, 28, 54, 98, 169, 156, 17, 146, 224, 51, 12, 165, 80, 5, 234, 9, 3, 196, 20, 137, 125, 82, 239, 19, 224, 232, 73, 187, 0, 49, | ||
243, 71, 178, 234, 91, 162, 187, 94, 235, 248, 45, 139, 194, 218, 246, 224, 65, 170, 140, 245, 26, 126, 230, 54, 79, 16, 19, 249, 152, 228, 234, 15, 211, 134, 23, 93, 60, 251, 94, 151, | ||
168, 98, 217, 232, 200, 140, 206, 113, 251, 41, 97, 70, 230, 12, 105, 220, 87, 137, 58, 117, 161, 183, 3, 206, 10, 185, 21, 137, 49, 151, 19, 209, 129, 240, 188, 88, 19, 159, 232, 35, 232, | ||
108, 63, 228, 1, 140, 67, 59, 204, 81, 239, 39, 86, 4, 158, 23, 11, 46, 138, 216, 161, 45, 56, 218, 52, 235, 162, 209, 130, 150, 229, 137, 80, 33, 103, 123, 108, 245, 252 | ||
}; | ||
var bodyDb = new byte[] { | ||
123, 34, 99, 112, 104, 34, 58, 34, 109, 56, 99, 111, 71, 109, 90, 107, 82, 88, 48, 53, 81, 77, 107, 83, 115, 67, 66, 88, 114, 76, 98, 69, 119, 69, 102, 105, 86, 74, 81, 116, 69, 53, 117, | ||
49, 49, 101, 107, 86, 76, 117, 89, 50, 52, 76, 56, 113, 51, 119, 119, 121, 83, 95, 115, 56, 84, 117, 121, 109, 79, 79, 77, 72, 106, 70, 97, 51, 82, 107, 69, 85, 72, 50, 76, 108, 81, 112, | ||
88, 105, 51, 105, 57, 87, 56, 89, 117, 82, 76, 80, 100, 118, 74, 90, 98, 117, 106, 68, 55, 71, 113, 117, 119, 103, 74, 107, 110, 97, 108, 65, 77, 115, 49, 52, 113, 98, 45, 109, 100, 89, | ||
53, 49, 48, 54, 108, 110, 69, 81, 99, 74, 50, 106, 48, 69, 100, 105, 82, 90, 98, 80, 82, 122, 106, 73, 99, 88, 66, 83, 53, 118, 119, 97, 56, 108, 51, 83, 114, 100, 116, 45, 87, 52, 102, | ||
82, 48, 81, 83, 108, 90, 89, 100, 114, 87, 71, 103, 68, 56, 73, 89, 70, 100, 107, 114, 110, 77, 104, 86, 99, 52, 52, 80, 75, 111, 57, 45, 100, 88, 81, 56, 56, 118, 121, 73, 89, 106, 110, | ||
77, 45, 102, 73, 53, 112, 45, 117, 95, 81, 73, 82, 87, 65, 100, 102, 106, 78, 70, 53, 87, 99, 50, 57, 89, 66, 106, 52, 53, 104, 68, 51, 103, 48, 65, 107, 82, 103, 90, 56, 117, 82, 55, 95, | ||
118, 108, 108, 88, 88, 104, 56, 55, 83, 112, 77, 98, 115, 65, 120, 83, 113, 90, 90, 97, 45, 106, 68, 90, 65, 97, 85, 120, 106, 75, 103, 68, 70, 88, 98, 83, 111, 119, 114, 66, 83, 110, 114, | ||
48, 109, 104, 48, 85, 104, 107, 99, 119, 67, 103, 89, 77, 87, 109, 50, 110, 90, 111, 70, 49, 72, 54, 88, 89, 66, 66, 72, 55, 50, 75, 54, 57, 95, 109, 74, 77, 95, 52, 78, 49, 98, 49, 112, | ||
65, 66, 121, 68, 66, 102, 49, 115, 113, 86, 122, 119, 89, 51, 119, 101, 57, 106, 79, 69, 78, 99, 48, 57, 79, 79, 84, 74, 110, 116, 110, 53, 66, 101, 112, 57, 78, 119, 80, 109, 118, 90, 99, | ||
56, 114, 71, 70, 104, 75, 102, 120, 77, 114, 72, 48, 105, 120, 100, 50, 67, 82, 119, 50, 89, 113, 109, 99, 69, 90, 76, 103, 77, 119, 121, 108, 85, 65, 88, 113, 67, 81, 80, 69, 70, 73, 108, | ||
57, 85, 117, 56, 84, 52, 79, 104, 74, 117, 119, 65, 120, 56, 48, 101, 121, 54, 108, 117, 105, 117, 49, 55, 114, 45, 67, 50, 76, 119, 116, 114, 50, 52, 69, 71, 113, 106, 80, 85, 97, 102, | ||
117, 89, 50, 84, 120, 65, 84, 45, 90, 106, 107, 54, 103, 95, 84, 104, 104, 100, 100, 80, 80, 116, 101, 108, 54, 104, 105, 50, 101, 106, 73, 106, 77, 53, 120, 45, 121, 108, 104, 82, 117, | ||
89, 77, 97, 100, 120, 88, 105, 84, 112, 49, 111, 98, 99, 68, 122, 103, 113, 53, 70, 89, 107, 120, 108, 120, 80, 82, 103, 102, 67, 56, 87, 66, 79, 102, 54, 67, 80, 111, 98, 68, 95, 107, 65, | ||
89, 120, 68, 79, 56, 120, 82, 55, 121, 100, 87, 66, 74, 52, 88, 67, 121, 54, 75, 50, 75, 69, 116, 79, 78, 111, 48, 54, 54, 76, 82, 103, 112, 98, 108, 105, 86, 65, 104, 90, 51, 116, 115, | ||
57, 102, 119, 34, 44, 34, 97, 108, 103, 34, 58, 51, 44, 34, 110, 110, 99, 34, 58, 34, 88, 106, 78, 99, 83, 103, 55, 100, 52, 69, 86, 117, 115, 98, 110, 114, 80, 84, 81, 53, 50, 106, 117, | ||
86, 101, 66, 49, 119, 75, 80, 85, 74, 34, 44, 34, 64, 116, 121, 112, 101, 34, 58, 34, 67, 114, 121, 112, 116, 111, 67, 105, 112, 104, 101, 114, 34, 125 | ||
}; | ||
|
||
var bodyTestString = System.Text.Encoding.UTF8.GetString(bodyTest); | ||
var bodyDbString = System.Text.Encoding.UTF8.GetString(bodyDb); | ||
|
||
var bodyDbPayload = System.Convert.FromBase64String("m8coGmZkRX05QMkSsCBXrLbEwEfiVJQtE5u11ekVLuY24L8q3wwyS/s8TuymOOMHjFa3RkEUH2LlQpXi3i9W8YuRLPdvJZbujD7GquwgJknalAMs14qb+mdY5106lnEQcJ2j0EdiRZbPRzjIcXBS5vwa8l3Srdt+W4fR0QSlZYdrWGgD8IYFdkrnMhVc44PKo9+dXQ88vyIYjnM+fI5p+u/QIRWAdfjNF5Wc29YBj45hD3g0AkRgZ8uR7/vllXXh87SpMbsAxSqZZa+jDZAaUxjKgDFXbSowrBSnr0mh0UhkcwCgYMWm2nZoF1H6XYBBH72K69/mJM/4N1b1pAByDBf1sqVzwY3we9jOENc09OOTJntn5Bep9NwPmvZc8rGFhKfxMrH0ixd2CRw2YqmcEZLgMwylUAXqCQPEFIl9Uu8T4OhJuwAx80ey6luiu17r+C2Lwtr24EGqjPUafuY2TxAT+Zjk6g/ThhddPPtel6hi2ejIjM5x+ylhRuYMadxXiTp1obcDzgq5FYkxlxPRgfC8WBOf6CPobD/kAYxDO8xR7ydWBJ4XCy6K2KEtONo066LRgpbliVAhZ3ts9fw="); | ||
|
||
var cryptoCipher = JsonSerializer.Deserialize<CryptoCipher>(bodyDbString, | ||
new JsonSerializerOptions { Converters = { new UrlSafeBase64ToByteArrayJsonConverter() } }); | ||
|
||
var resString = cryptoCipher.cph; | ||
resString = resString.Replace('-', '+'); | ||
resString = resString.Replace('_', '/'); | ||
resString += '='; | ||
|
||
var deserializedSecret = JsonSerializer.Deserialize<Secret>("{\"key\":\"yS6vFC70epmYFoUAIopluLmJJUH58FVGnwI3AR1fPZ8\",\"alg\":3}", | ||
new JsonSerializerOptions { Converters = { new UrlSafeBase64ToByteArrayJsonConverter() } }); | ||
var key = deserializedSecret.key; | ||
|
||
|
||
|
||
//var bodyOfMessage = bodyDb; | ||
//var bodyOfMessageAsString = Encoding.UTF8.GetString(bodyDb); | ||
//var deserializedBodyOfMessage = JsonSerializer.Deserialize<CryptoCipher>(bodyOfMessageAsString, | ||
// new JsonSerializerOptions { Converters = { new UrlSafeBase64ToByteArrayJsonConverter() } }); | ||
|
||
//var bodyToDecrypt = Convert.FromBase64String(deserializedBodyOfMessage.cph); | ||
//var nonceToDecryptWith = Convert.FromBase64String(deserializedBodyOfMessage.nnc); | ||
|
||
var res0 = LibsodiumSymmetricEncrypter.DecryptXChaCha20Poly1305(bodyTest, nonce, secret); | ||
var res1 = LibsodiumSymmetricEncrypter.DecryptXChaCha20Poly1305(Convert.FromBase64String(resString), nonce, secret); | ||
var res2 = LibsodiumSymmetricEncrypter.DecryptXChaCha20Poly1305(bodyDb, "{\"key\":\"yS6vFC70epmYFoUAIopluLmJJUH58FVGnwI3AR1fPZ8\",\"alg\":3}"); | ||
|
||
1.Should().Be(1); | ||
res0.Should().BeEquivalentTo(res2); | ||
} | ||
|
||
[Fact] | ||
public void Clean_test() | ||
{ | ||
// Arrange | ||
var body = new byte[] { | ||
123, 34, 99, 112, 104, 34, 58, 34, 109, 56, 99, 111, 71, 109, 90, 107, 82, 88, 48, 53, 81, 77, 107, 83, 115, 67, 66, 88, 114, 76, 98, 69, 119, 69, 102, 105, 86, 74, 81, 116, 69, 53, 117, | ||
49, 49, 101, 107, 86, 76, 117, 89, 50, 52, 76, 56, 113, 51, 119, 119, 121, 83, 95, 115, 56, 84, 117, 121, 109, 79, 79, 77, 72, 106, 70, 97, 51, 82, 107, 69, 85, 72, 50, 76, 108, 81, 112, | ||
88, 105, 51, 105, 57, 87, 56, 89, 117, 82, 76, 80, 100, 118, 74, 90, 98, 117, 106, 68, 55, 71, 113, 117, 119, 103, 74, 107, 110, 97, 108, 65, 77, 115, 49, 52, 113, 98, 45, 109, 100, 89, | ||
53, 49, 48, 54, 108, 110, 69, 81, 99, 74, 50, 106, 48, 69, 100, 105, 82, 90, 98, 80, 82, 122, 106, 73, 99, 88, 66, 83, 53, 118, 119, 97, 56, 108, 51, 83, 114, 100, 116, 45, 87, 52, 102, | ||
82, 48, 81, 83, 108, 90, 89, 100, 114, 87, 71, 103, 68, 56, 73, 89, 70, 100, 107, 114, 110, 77, 104, 86, 99, 52, 52, 80, 75, 111, 57, 45, 100, 88, 81, 56, 56, 118, 121, 73, 89, 106, 110, | ||
77, 45, 102, 73, 53, 112, 45, 117, 95, 81, 73, 82, 87, 65, 100, 102, 106, 78, 70, 53, 87, 99, 50, 57, 89, 66, 106, 52, 53, 104, 68, 51, 103, 48, 65, 107, 82, 103, 90, 56, 117, 82, 55, 95, | ||
118, 108, 108, 88, 88, 104, 56, 55, 83, 112, 77, 98, 115, 65, 120, 83, 113, 90, 90, 97, 45, 106, 68, 90, 65, 97, 85, 120, 106, 75, 103, 68, 70, 88, 98, 83, 111, 119, 114, 66, 83, 110, 114, | ||
48, 109, 104, 48, 85, 104, 107, 99, 119, 67, 103, 89, 77, 87, 109, 50, 110, 90, 111, 70, 49, 72, 54, 88, 89, 66, 66, 72, 55, 50, 75, 54, 57, 95, 109, 74, 77, 95, 52, 78, 49, 98, 49, 112, | ||
65, 66, 121, 68, 66, 102, 49, 115, 113, 86, 122, 119, 89, 51, 119, 101, 57, 106, 79, 69, 78, 99, 48, 57, 79, 79, 84, 74, 110, 116, 110, 53, 66, 101, 112, 57, 78, 119, 80, 109, 118, 90, 99, | ||
56, 114, 71, 70, 104, 75, 102, 120, 77, 114, 72, 48, 105, 120, 100, 50, 67, 82, 119, 50, 89, 113, 109, 99, 69, 90, 76, 103, 77, 119, 121, 108, 85, 65, 88, 113, 67, 81, 80, 69, 70, 73, 108, | ||
57, 85, 117, 56, 84, 52, 79, 104, 74, 117, 119, 65, 120, 56, 48, 101, 121, 54, 108, 117, 105, 117, 49, 55, 114, 45, 67, 50, 76, 119, 116, 114, 50, 52, 69, 71, 113, 106, 80, 85, 97, 102, | ||
117, 89, 50, 84, 120, 65, 84, 45, 90, 106, 107, 54, 103, 95, 84, 104, 104, 100, 100, 80, 80, 116, 101, 108, 54, 104, 105, 50, 101, 106, 73, 106, 77, 53, 120, 45, 121, 108, 104, 82, 117, | ||
89, 77, 97, 100, 120, 88, 105, 84, 112, 49, 111, 98, 99, 68, 122, 103, 113, 53, 70, 89, 107, 120, 108, 120, 80, 82, 103, 102, 67, 56, 87, 66, 79, 102, 54, 67, 80, 111, 98, 68, 95, 107, 65, | ||
89, 120, 68, 79, 56, 120, 82, 55, 121, 100, 87, 66, 74, 52, 88, 67, 121, 54, 75, 50, 75, 69, 116, 79, 78, 111, 48, 54, 54, 76, 82, 103, 112, 98, 108, 105, 86, 65, 104, 90, 51, 116, 115, | ||
57, 102, 119, 34, 44, 34, 97, 108, 103, 34, 58, 51, 44, 34, 110, 110, 99, 34, 58, 34, 88, 106, 78, 99, 83, 103, 55, 100, 52, 69, 86, 117, 115, 98, 110, 114, 80, 84, 81, 53, 50, 106, 117, | ||
86, 101, 66, 49, 119, 75, 80, 85, 74, 34, 44, 34, 64, 116, 121, 112, 101, 34, 58, 34, 67, 114, 121, 112, 116, 111, 67, 105, 112, 104, 101, 114, 34, 125 | ||
}; | ||
var secretKey = "{\"key\":\"yS6vFC70epmYFoUAIopluLmJJUH58FVGnwI3AR1fPZ8\",\"alg\":3}"; | ||
|
||
var identityAddress = TestDataGenerator.CreateRandomIdentityAddress(); | ||
var deviceId = TestDataGenerator.CreateRandomDeviceId(); | ||
|
||
var message = new Message(identityAddress, deviceId, null, body, new List<Attachment>(), new List<RecipientInformation>()); | ||
var expectedBody = | ||
"{\"@type\":\"MessageSigned\",\"message\":\"{\\\"@type\\\":\\\"MessageContentWrapper\\\",\\\"attachments\\\":[],\\\"content\\\":{\\\"content\\\":\\\"TestContent\\\"},\\\"createdAt\\\":\\\"2024-02-06T22:18:39.470Z\\\",\\\"recipients\\\":[\\\"id19khDhsE7Ak18BgMHEba7iczUdcg4Zjmew\\\"]}\",\"signatures\":[{\"recipient\":\"id19khDhsE7Ak18BgMHEba7iczUdcg4Zjmew\",\"signature\":\"{\\\"sig\\\":\\\"S8wFVW_NHwmydRIpDGgjbnm5YXICimfjBaupZO7T3firEIuu_NWogZwcgXMzC5YehXyERasueSVOrkGR0PnaDg\\\",\\\"alg\\\":1}\"}]}"; | ||
|
||
// Act | ||
var decryptedBody = message.DecryptBody(secretKey); | ||
|
||
// Assert | ||
decryptedBody.Should().BeEquivalentTo(expectedBody); | ||
} | ||
|
||
public class CryptoCipher | ||
{ | ||
public string cph { get; set; } | ||
public int alg { get; set; } | ||
public string nnc { get; set; } | ||
} | ||
|
||
private class Secret | ||
{ | ||
public string key { get; set; } | ||
} | ||
} |