Add unit test for linked data signatures
This commit is contained in:
parent
2c9e40f63e
commit
9d70c68dbd
8 changed files with 89 additions and 47 deletions
|
@ -32,7 +32,7 @@ public class ActivityPubController(ILogger<ActivityPubController> logger, Databa
|
|||
var user = await db.Users.FirstOrDefaultAsync(p => p.Id == id);
|
||||
if (user == null) return NotFound();
|
||||
var rendered = await userRenderer.Render(user);
|
||||
var compacted = LDHelpers.Compact(rendered);
|
||||
var compacted = LdHelpers.Compact(rendered);
|
||||
return Ok(compacted);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ public class ActivityPubService(HttpClient client, HttpRequestService httpRqSvc)
|
|||
var input = await response.Content.ReadAsStringAsync();
|
||||
var json = JsonConvert.DeserializeObject<JObject?>(input, JsonSerializerSettings);
|
||||
|
||||
var res = LDHelpers.Expand(json);
|
||||
var res = LdHelpers.Expand(json);
|
||||
if (res == null) throw new Exception("Failed to expand JSON-LD object");
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ using VDS.RDF.Writing.Formatting;
|
|||
|
||||
namespace Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||
|
||||
public static class LDHelpers {
|
||||
public static class LdHelpers {
|
||||
private static readonly Dictionary<string, RemoteDocument> ContextCache = new() {
|
||||
{
|
||||
"https://www.w3.org/ns/activitystreams", new RemoteDocument {
|
||||
|
@ -53,6 +53,7 @@ public static class LDHelpers {
|
|||
}
|
||||
|
||||
public static JObject? Compact(object obj) => Compact(JToken.FromObject(obj));
|
||||
public static JArray? Expand(object obj) => Expand(JToken.FromObject(obj));
|
||||
public static JObject? Compact(JToken? json) => JsonLdProcessor.Compact(json, DefaultContext, Options);
|
||||
public static JArray? Expand(JToken? json) => JsonLdProcessor.Expand(json, Options);
|
||||
public static string Canonicalize(JArray json) => JsonLdProcessor.Canonicalize(json);
|
||||
|
|
|
@ -11,28 +11,33 @@ using VC = Iceshrimp.Backend.Core.Federation.ActivityStreams.Types.ValueObjectCo
|
|||
namespace Iceshrimp.Backend.Core.Federation.Cryptography;
|
||||
|
||||
public static class LdSignature {
|
||||
public static async Task<bool> Verify(JArray activity, string key) {
|
||||
foreach (var act in activity) {
|
||||
var options = act["https://w3id.org/security#signature"];
|
||||
public static Task<bool> Verify(JArray activity, string key) {
|
||||
if (activity.ToArray() is not [JObject obj]) throw new Exception("Invalid activity");
|
||||
return Verify(obj, key);
|
||||
}
|
||||
|
||||
public static async Task<bool> Verify(JObject activity, string key) {
|
||||
var options = activity["https://w3id.org/security#signature"];
|
||||
if (options?.ToObject<SignatureOptions[]>() is not { Length: 1 } signatures) return false;
|
||||
var signature = signatures[0];
|
||||
if (signature.Type is not ["_:RsaSignature2017"]) return false;
|
||||
if (signature.Signature is null) return false;
|
||||
|
||||
var signatureData = await GetSignatureData(act, options);
|
||||
var signatureData = await GetSignatureData(activity, options);
|
||||
if (signatureData is null) return false;
|
||||
|
||||
var rsa = RSA.Create();
|
||||
rsa.ImportFromPem(key);
|
||||
var verify = rsa.VerifyData(signatureData, Convert.FromBase64String(signature.Signature),
|
||||
return rsa.VerifyData(signatureData, Convert.FromBase64String(signature.Signature),
|
||||
HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
|
||||
if (!verify) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
public static Task<JObject> Sign(JArray activity, string key, string? creator) {
|
||||
if (activity.ToArray() is not [JObject obj]) throw new Exception("Invalid activity");
|
||||
return Sign(obj, key, creator);
|
||||
}
|
||||
|
||||
public static async Task<JToken> Sign(JToken data, string key, string? creator) {
|
||||
public static async Task<JObject> Sign(JObject activity, string key, string? creator) {
|
||||
var options = new SignatureOptions {
|
||||
Created = DateTime.Now,
|
||||
Creator = creator,
|
||||
|
@ -41,7 +46,7 @@ public static class LdSignature {
|
|||
Domain = null,
|
||||
};
|
||||
|
||||
var signatureData = await GetSignatureData(data, options);
|
||||
var signatureData = await GetSignatureData(activity, options);
|
||||
if (signatureData == null) throw new NullReferenceException("Signature data must not be null");
|
||||
|
||||
var rsa = RSA.Create();
|
||||
|
@ -51,14 +56,13 @@ public static class LdSignature {
|
|||
|
||||
options.Signature = signature;
|
||||
|
||||
if (data is not JObject obj) throw new Exception();
|
||||
obj.Add("https://w3id.org/security#signature", JToken.FromObject(options));
|
||||
activity.Add("https://w3id.org/security#signature", JToken.FromObject(options));
|
||||
|
||||
return obj;
|
||||
return LdHelpers.Expand(activity)?[0] as JObject ?? throw new Exception("Failed to expand signed activity");
|
||||
}
|
||||
|
||||
private static Task<byte[]?> GetSignatureData(JToken data, SignatureOptions options) =>
|
||||
GetSignatureData(data, LDHelpers.Expand(JObject.FromObject(options))!);
|
||||
GetSignatureData(data, LdHelpers.Expand(JObject.FromObject(options))!);
|
||||
|
||||
private static async Task<byte[]?> GetSignatureData(JToken data, JToken options) {
|
||||
if (data is not JObject inputData) return null;
|
||||
|
@ -71,8 +75,8 @@ public static class LdSignature {
|
|||
|
||||
inputData.Remove("https://w3id.org/security#signature");
|
||||
|
||||
var canonicalData = LDHelpers.Canonicalize(inputData);
|
||||
var canonicalOptions = LDHelpers.Canonicalize(inputOptions);
|
||||
var canonicalData = LdHelpers.Canonicalize(inputData);
|
||||
var canonicalOptions = LdHelpers.Canonicalize(inputOptions);
|
||||
|
||||
var dataHash = await DigestHelpers.Sha256Digest(canonicalData);
|
||||
var optionsHash = await DigestHelpers.Sha256Digest(canonicalOptions);
|
||||
|
|
34
Iceshrimp.Tests/Cryptography/LdSignatureTests.cs
Normal file
34
Iceshrimp.Tests/Cryptography/LdSignatureTests.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using System.Security.Cryptography;
|
||||
using FluentAssertions;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||
using Iceshrimp.Backend.Core.Federation.Cryptography;
|
||||
using Iceshrimp.Backend.Core.Helpers;
|
||||
|
||||
namespace Iceshrimp.Tests.Cryptography;
|
||||
|
||||
[TestClass]
|
||||
public class LdSignatureTests {
|
||||
[TestMethod]
|
||||
public async Task RoundtripTest() {
|
||||
var keypair = RSA.Create();
|
||||
|
||||
var actor = new ASActor {
|
||||
Id = $"https://example.org/users/{IdHelpers.GenerateSlowflakeId()}",
|
||||
Type = ["https://www.w3.org/ns/activitystreams#Person"],
|
||||
Url = new ASLink($"https://example.org/@test"),
|
||||
Username = "test",
|
||||
DisplayName = "Test account",
|
||||
IsCat = false,
|
||||
IsDiscoverable = true,
|
||||
IsLocked = true
|
||||
};
|
||||
|
||||
var expanded = LdHelpers.Expand(actor);
|
||||
|
||||
var signed = await LdSignature.Sign(expanded!, keypair.ExportRSAPrivateKeyPem(), actor.Id + "#main-key");
|
||||
var verify = await LdSignature.Verify(signed, keypair.ExportRSAPublicKeyPem());
|
||||
|
||||
verify.Should().BeTrue();
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
namespace Iceshrimp.Tests.Cryptography;
|
||||
|
||||
[TestClass]
|
||||
public class LdSignatures {
|
||||
[TestMethod]
|
||||
public void TestMethod1() {
|
||||
|
||||
}
|
||||
}
|
|
@ -10,10 +10,19 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.0.4" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.0.4" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Iceshrimp.Backend\Iceshrimp.Backend.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="configuration.ini" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
3
Iceshrimp.Tests/configuration.ini
Normal file
3
Iceshrimp.Tests/configuration.ini
Normal file
|
@ -0,0 +1,3 @@
|
|||
[Instance]
|
||||
WebDomain=shrimp.example.org
|
||||
AccountDomain=example.org
|
Loading…
Add table
Reference in a new issue