Skip to content

Commit

Permalink
Backbone-specific addresses (#570)
Browse files Browse the repository at this point in the history
* feat: add 100 char-long identityAddresses

* chore: add missing config

* chore: fix erroneous migrations

* feat: add 100 char-long identityAddresses on SqlServer

* chore: fix erroneous migrations' name

* chore: fix erroneous migrations' name

* chore: attempt to remove identity assertions from transport tests

* chore: fix sed

* chore: fix erroneous migrations' name

* chore: use backbone-specific-addresses branch of transport tests

* chore: branch → ref

* chore: fix branch name

* chore: backbone sln merge

* chore: remove realm, change url to prod.enmeshed.eu where applicable

* chore: fix unit tests after removing realm from identity

* chore: remove AddressPrefix from config files

* chore: remove AddressPrefix from config files

* chore: fix formatting

* chore: remove second needless migration

* fix: remove old configuration and update messages to use InstanceUrl, from configuration

* chore: move migration order
hopefully this fixes the pipeline for now. However, this must be fixed later

* feat: implement new IdentityAddress generation algorithm

* chore: reformat

* chore: simplify isValid

* feat: change address length to 80

* chore: fix migration name

* chore: fix migration name

* chore: fix old 100 to 80 chars

* chore: trigger pipeline

* fix: pipeline test file

* fix: missing defaultschema for migrations

* fix: missing defaultschema for migrations

* fix: max length set to 100

* chore: add InstanceUrl to test appsettings

* chore: transport tests should still use the backbone-specific-addresses branch

* chore: add InstanceUrl to test appsettings for event handler

* fix: bad feature/backbone-specific-addresses branch name

* fix: 100 → 80 chars

* chore: remove out-of-date migrations

* feat: Identity80 migrations for relationships module

* chore: more restrictive regex. usage of return value instead of relying on exceptions

* chore: fix migration file names

* fix: exception thrown when address does not match the regex

* chore: identitySpecificPart must be a hex representation of bytes

* refactor: turn regex into a compiled one

* chorte: remove unused pattern

* chore: use named regex groups

* chore: extract CHECKSUM_LENGTH, create addressWithoutChecksumregex group

* chore: optimize regex

* refactor: rename variable

---------

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
Co-authored-by: Timo Notheisen <[email protected]>
Co-authored-by: Timo Notheisen <[email protected]>
  • Loading branch information
4 people authored May 8, 2024
1 parent c68f03d commit a06996b
Show file tree
Hide file tree
Showing 79 changed files with 6,954 additions and 236 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ jobs:
with:
repository: nmshd/runtime
path: runtime
ref: release/v5
ref: feature/backbone-specific-addresses
- name: Run Consumer API
run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.sqlserver.yml up -d
- name: Install runtime dependencies
Expand Down Expand Up @@ -153,7 +153,7 @@ jobs:
with:
repository: nmshd/runtime
path: runtime
ref: release/v5
ref: feature/backbone-specific-addresses
- name: Run Consumer API
run: docker compose -f ./backbone/.ci/docker-compose.test.yml -f ./backbone/.ci/docker-compose.test.postgres.yml up -d
- name: Install runtime dependencies
Expand Down
2 changes: 1 addition & 1 deletion AdminApi/src/AdminApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
},
"Devices": {
"Application": {
"AddressPrefix": "id1",
"InstanceUrl": "prod.enmeshed.eu",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand Down
1 change: 0 additions & 1 deletion AdminApi/src/AdminApi/appsettings.override.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"Modules": {
"Devices": {
"Application": {
"AddressPrefix": "id1",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand Down
3 changes: 2 additions & 1 deletion Backbone.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeEditing/Intellisense/CodeCompletion/IntelliSenseCompletingCharacters/CSharpCompletingCharacters/UpgradedFromVSSettings/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToConstant_002ELocal/@EntryIndexedValue">SUGGESTION</s:String>
<s:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConvertToPrimaryConstructor/@EntryIndexedValue">DO_NOT_SHOW</s:String>
Expand Down Expand Up @@ -32,6 +32,7 @@
<s:Boolean x:Key="/Default/UserDictionary/Words/=appsettings/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Datawallet/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Datawallets/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=dids/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Deleters/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Dtos/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ecdsa/@EntryIndexedValue">True</s:Boolean>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
using System.Globalization;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using Backbone.BuildingBlocks.Domain;
using Backbone.BuildingBlocks.Domain.StronglyTypedIds.Records;
using SimpleBase;

namespace Backbone.DevelopmentKit.Identity.ValueObjects;

[Serializable]
[TypeConverter(typeof(IdentityAddressTypeConverter))]
public record IdentityAddress : StronglyTypedId
public partial record IdentityAddress : StronglyTypedId
{
public const int MAX_LENGTH = 36;
public const int MAX_LENGTH = 80;
private const int CHECKSUM_LENGTH = 2;
private const string CHECKSUM_LENGTH_S = "2";

private IdentityAddress(string stringValue) : base(stringValue)
{
Expand Down Expand Up @@ -43,39 +45,45 @@ public static bool IsValid(string? stringValue)
{
if (stringValue == null) return false;

var lengthIsValid = stringValue.Length <= MAX_LENGTH;
if (stringValue.Length > MAX_LENGTH)
return false;

var realm = stringValue[..3];
var matches = IdentityAddressValidatorRegex().Matches(stringValue);

var concatenation = Base58.Bitcoin.Decode(stringValue.AsSpan(3)).ToArray();
var hashedPublicKey = concatenation[..20];
var givenChecksum = concatenation[20..];
if (matches.Count == 0) return false;

var realmBytes = Encoding.UTF8.GetBytes(realm);
var correctChecksum = CalculateChecksum(realmBytes, hashedPublicKey);
var matchGroups = matches.First().Groups;

var checksumIsValid = givenChecksum.SequenceEqual(correctChecksum);
if (!matchGroups.TryGetValue("checksum", out var givenChecksum))
return false;

return lengthIsValid && checksumIsValid;
if (!matchGroups.TryGetValue("addressWithoutChecksum", out var addressWithoutChecksum))
return false;

var expectedChecksum = CalculateChecksum(addressWithoutChecksum.Value);

var checksumIsValid = givenChecksum.Value == expectedChecksum;

return checksumIsValid;
}

public static IdentityAddress Create(byte[] publicKey, string realm)
public static IdentityAddress Create(byte[] publicKey, string instanceUrl)
{
var hashedPublicKey = SHA256.Create().ComputeHash(SHA512.Create().ComputeHash(publicKey))[..20];
var realmBytes = Encoding.UTF8.GetBytes(realm);
var checksum = CalculateChecksum(realmBytes, hashedPublicKey);
var concatenation = hashedPublicKey.Concat(checksum).ToArray();
var address = realm + Base58.Bitcoin.Encode(concatenation);
var hashedPublicKey = SHA256.HashData(SHA512.HashData(publicKey))[..10];

var identitySpecificPart = Hex(hashedPublicKey);

return new IdentityAddress(address);
var mainPhrase = $"did:e:{instanceUrl}:dids:{identitySpecificPart}";
var checksum = CalculateChecksum(mainPhrase);

return new IdentityAddress((mainPhrase + checksum).ToLower());
}

private static byte[] CalculateChecksum(byte[] realmBytes, byte[] hashedPublicKey)
private static string CalculateChecksum(string phrase) => Hex(SHA256.HashData(Encoding.ASCII.GetBytes(phrase)))[..CHECKSUM_LENGTH];

private static string Hex(byte[] bytes)
{
var checksumSource = realmBytes.Concat(hashedPublicKey).ToArray();
var checksumHash = SHA256.Create().ComputeHash(SHA512.Create().ComputeHash(checksumSource));
var checksum = checksumHash[..4];
return checksum;
return Convert.ToHexString(bytes).ToLower();
}

public override string ToString()
Expand Down Expand Up @@ -116,5 +124,8 @@ public static implicit operator IdentityAddress(string stringValue)
return ParseUnsafe(stringValue);
}

[GeneratedRegex($@"^(?<addressWithoutChecksum>did:e:(?<instanceUrl>(?:[a-z0-9]+\.)+[a-z]{{2,}}):dids:(?<identitySpecificPart>[0-9abcdef]{{20}}))(?<checksum>[0-9abcdef]{{{CHECKSUM_LENGTH_S}}})$")]
private static partial Regex IdentityAddressValidatorRegex();

#endregion
}
2 changes: 1 addition & 1 deletion BuildingBlocks/src/UnitTestTools/Data/TestDataGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public static string GenerateString(int resultLength, char[]? chars = null)

public static IdentityAddress CreateRandomIdentityAddress()
{
return IdentityAddress.Create(CreateRandomBytes(), "id1");
return IdentityAddress.Create(CreateRandomBytes(), "prod.enmeshed.eu");
}

public static DeviceId CreateRandomDeviceId()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,46 @@ namespace Backbone.DevelopmentKit.Identity.Tests;
public class IdentityAddressTests
{
[Theory]
[InlineData("fj0o9eOiPRswTZL6j9lE9TRvpDDnPRMF0gJeahz/W2c=", "id1QF24Gk2DfqCywRS7NpeH5iu7D4xvu6qv1")]
[InlineData("jRxGfZtQ8a90TmKCGk+dhuX1CBjgoXuldhNPwrjpWsw=", "id1HwY1TuyVBp3CmY3h18yTt1CKyu5qwB9wj")]
[InlineData("PEODpwvi7KxIVa4qeUXia9apMFvPMktdDHiDitlfbjE=", "id1LMp4k1XwxZ3WFXdAn9y12tv1ofe5so4kM")]
[InlineData("mJGmNbxiVZAPToRuk9O3NvdfsWl6V+7wzIc+/57bU08=", "id1McegXycvRoiJppS2LG25phn3jNveckFUL")]
[InlineData("l68K/zdNp1VLoswcHAqN6QUFwCMU6Yvzf7XiW2m1hRY=", "id193k6K5cJr94WJEWYb6Kei8zp5CGPyrQLS")]
[InlineData("Gl8XTo8qFuUM+ksXixwp4g/jf3H/hU1F8ETuYaHCM5I=", "id1BLrHAgDpimtLcGJGssMSm7bJHsvVe7CN")]
[InlineData("rIS4kAzHXT7GgCA6Qm1ANlwM3x12QMSkeprHb6tjPyc=", "id1NjGvLfWPrQ34PXWRBNiTfXv9DFiDQHExx")]
[InlineData("hg/cbeBvfNrMiJ0dW1AtWC4IQwG4gkuhzG2+z6bAoRU=", "id1Gda4aTXiBX9Pyc8UnmLaG44cX46umjnea")]
[InlineData("kId+qWen/lKeTdyxcIQhkzvvvTU8wIJECfWUWbmRQRY=", "id17RDEphijMPFGLbhqLWWgJfatBANMruC8f")]
[InlineData("NcqlzTEpSlKX9gmNBv41EjPRHpaNYwt0bxqh1bgyJzA=", "id19meHs4Di7JYNXoRPx9bFD6FUcpHFo3mBi")]
[InlineData("49fWA+kzWNdCFdo92imTiQ4vUUJsPPLNlcB9udC4ooE=", "id1c711BBi4yqV9wrLBVKxRSNFayfAm3Eib")]
// ReSharper disable StringLiteralTypo
[InlineData("49fWA+kzWNdCFdo92imTiQ4vUUJsPPLNlcB9udC4ooE=", "did:e:prod.enmeshed.eu:dids:06a391378e5df5c1399f77")]
[InlineData("fj0o9eOiPRswTZL6j9lE9TRvpDDnPRMF0gJeahz/W2c=", "did:e:prod.enmeshed.eu:dids:fef1992c5e529adc413288")]
[InlineData("Gl8XTo8qFuUM+ksXixwp4g/jf3H/hU1F8ETuYaHCM5I=", "did:e:prod.enmeshed.eu:dids:01f4bab09d757578bb4994")]
[InlineData("hg/cbeBvfNrMiJ0dW1AtWC4IQwG4gkuhzG2+z6bAoRU=", "did:e:prod.enmeshed.eu:dids:ab7475ba4070f29ce286fd")]
[InlineData("jRxGfZtQ8a90TmKCGk+dhuX1CBjgoXuldhNPwrjpWsw=", "did:e:prod.enmeshed.eu:dids:b9d25bd0a2bbd3aa48437c")]
[InlineData("kId+qWen/lKeTdyxcIQhkzvvvTU8wIJECfWUWbmRQRY=", "did:e:prod.enmeshed.eu:dids:4664f42d7ca6480db07fdb")]
[InlineData("l68K/zdNp1VLoswcHAqN6QUFwCMU6Yvzf7XiW2m1hRY=", "did:e:prod.enmeshed.eu:dids:5845cf29fbda2897892a9a")]
[InlineData("mJGmNbxiVZAPToRuk9O3NvdfsWl6V+7wzIc+/57bU08=", "did:e:prod.enmeshed.eu:dids:e2208784ee2769c5d9684d")]
[InlineData("NcqlzTEpSlKX9gmNBv41EjPRHpaNYwt0bxqh1bgyJzA=", "did:e:prod.enmeshed.eu:dids:60326ff5075e0d7378990c")]
[InlineData("PEODpwvi7KxIVa4qeUXia9apMFvPMktdDHiDitlfbjE=", "did:e:prod.enmeshed.eu:dids:d459ff2144f0eac7aff554")]
[InlineData("rIS4kAzHXT7GgCA6Qm1ANlwM3x12QMSkeprHb6tjPyc=", "did:e:prod.enmeshed.eu:dids:ee5966a158f1dc4de5bd5c")]
// ReSharper enable StringLiteralTypo
public void AddressIsCreatedCorrectly2(string publicKey, string expectedAddress)
{
var address = IdentityAddress.Create(Convert.FromBase64String(publicKey), "id1");
var address = IdentityAddress.Create(Convert.FromBase64String(publicKey), "prod.enmeshed.eu");

address.Value.Should().Be(expectedAddress);
}

[Theory]
// ReSharper disable StringLiteralTypo
[InlineData("did:e:enmeshedeu:dids:06a391378e5df5c1399f77")]
[InlineData("did:e:prod.ENMESHED.eu:dids:fef1992c5e529adc413288")]
[InlineData("ID154565465468435134684648ffef1992ca5e529adc413288")]
[InlineData("dod:e:prod.enmeshed.eu:dids:ee5966a158f1dc4de5bd5c")]
[InlineData("did:e:prod.enmeshed.eu:dids:nonhexchars11")]
[InlineData("did:e:prod.enmeshed.eu:dids:eCa432178Ca417a1311")] // capital letters
[InlineData("did:e:prod.enmeshed.eu:dids:ee5966a158f1dc4de5bd5cee5966a158f1dc4de5bd5c")]
// ReSharper enable StringLiteralTypo
public void IsValidReturnsFalseForInvalidAddress(string identityAddress)
{
IdentityAddress.IsValid(identityAddress).Should().BeFalse();
}

[Fact]
public void AddressIsCreatedCorrectly()
{
var testData = TestData.Valid();
var address = IdentityAddress.Create(testData.PublicKey, testData.Realm);
var address = IdentityAddress.Create(testData.PublicKey, testData.InstanceUrl);

address.Value.Should().Be(testData.Address);
}
Expand All @@ -38,7 +55,7 @@ public void AddressIsCreatedCorrectly()
public void EfConverterWorksCorrectly()
{
var testData = TestData.Valid();
var address = IdentityAddress.Create(testData.PublicKey, testData.Realm);
var address = IdentityAddress.Create(testData.PublicKey, testData.InstanceUrl);

address.Value.Should().Be(testData.Address);
}
Expand All @@ -52,15 +69,6 @@ public void ValidAddressesAreAccepted()
isValid.Should().BeTrue();
}

[Fact]
public void AddressesWithInvalidRealmAreDeclined()
{
var testData = TestData.WithInvalidRealm();
var isValid = IdentityAddress.IsValid(testData.Address);

isValid.Should().BeFalse();
}

[Fact]
public void AddressesWithInvalidChecksumAreDeclined()
{
Expand All @@ -82,59 +90,43 @@ public void AddressesWithInvalidMainPartAreDeclined()

internal class TestData
{
public required string Realm { get; set; }
public required string InstanceUrl { get; set; }
public required byte[] PublicKey { get; set; }

public required string Address { get; set; }

public required string Checksum { get; set; }
public required string MainPart { get; set; }

public static TestData Valid()
{
return new TestData
{
Address = "id18uSgVGTSNqECvt1DJM3bZg6U8p6RSjott",
PublicKey = Convert.FromBase64String("tB9KFp/YqHrom3m5qUuZsd6l30DkaNjN14SxRw7YZuI="),
Realm = "id1",
Checksum = "jott",
MainPart = "8uSgVGTSNqECvt1DJM3bZg6U8p6RS"
};
}

public static TestData WithInvalidRealm()
{
return new TestData
{
Address = "id08uSgVGTSNqECvt1DJM3bZg6U8p6RSjott",
Address = "did:e:prod.enmeshed.eu:dids:56b3f2a0c202e27229aa87",
PublicKey = Convert.FromBase64String("tB9KFp/YqHrom3m5qUuZsd6l30DkaNjN14SxRw7YZuI="),
Realm = "id0",
Checksum = "jott",
MainPart = "8uSgVGTSNqECvt1DJM3bZg6U8p6RS"
Checksum = "87",
InstanceUrl = "prod.enmeshed.eu"
};
}

public static TestData WithInvalidMainPart()
{
return new TestData
{
Address = "id07uSgVGTSNqECvt1DJM3bZg6U8p6RSjott",
Address = "did:e:prod.enmeshed.eu:dids:56b3f2a0c202e27d39aa87",
PublicKey = Convert.FromBase64String("tB9KFp/YqHrom3m5qUuZsd6l30DkaNjN14SxRw7YZuI="),
Realm = "id0",
Checksum = "jott",
MainPart = "7uSgVGTSNqECvt1DJM3bZg6U8p6RS"
Checksum = "87",
InstanceUrl = "prod.enmeshed.eu"
};
}

public static TestData WithInvalidChecksum()
{
return new TestData
{
Address = "id08uSgVGTSNqECvt1DJM3bZg6U8p6RSiott",
Address = "did:e:prod.enmeshed.eu:dids:56b3f2a0c202e27229aa55",
PublicKey = Convert.FromBase64String("tB9KFp/YqHrom3m5qUuZsd6l30DkaNjN14SxRw7YZuI="),
Realm = "id0",
Checksum = "iott",
MainPart = "8uSgVGTSNqECvt1DJM3bZg6U8p6RS"
Checksum = "55",
InstanceUrl = "prod.enmeshed.eu"
};
}
}
12 changes: 12 additions & 0 deletions ConsumerApi.Tests.Integration/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,17 @@
"ClientId": "test",
"ClientSecret": "test"
}
},
"Modules": {
"Devices": {
"Application": {
"InstanceUrl": "pipeline.test.enmeshed.eu"
}
},
"Messages": {
"Application": {
"InstanceUrl": "pipeline.test.enmeshed.eu"
}
}
}
}
4 changes: 2 additions & 2 deletions ConsumerApi/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"Devices": {
"Application": {
"AddressPrefix": "id1",
"InstanceUrl": "prod.enmeshed.eu",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand All @@ -45,7 +45,7 @@
},
"Messages": {
"Application": {
"AddressPrefix": "id1",
"InstanceUrl": "prod.enmeshed.eu",
"MaxNumberOfUnreceivedMessagesFromOneSender": 20,
"Pagination": {
"DefaultPageSize": 50,
Expand Down
2 changes: 2 additions & 0 deletions EventHandlerService/src/EventHandlerService/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"Devices": {
"Application": {
"AddressPrefix": "id1",
"InstanceUrl": "pipeline.test.enmeshed.eu",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand All @@ -39,6 +40,7 @@
"Messages": {
"Application": {
"AddressPrefix": "id1",
"InstanceUrl": "pipeline.test.enmeshed.eu",
"MaxNumberOfUnreceivedMessagesFromOneSender": 20,
"Pagination": {
"DefaultPageSize": 50,
Expand Down
13 changes: 6 additions & 7 deletions Jobs/src/Job.IdentityDeletion/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
},
"Devices": {
"Application": {
"AddressPrefix": "id1",
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
Expand All @@ -38,12 +37,12 @@
},
"Messages": {
"Application": {
"AddressPrefix": "id1",
"MaxNumberOfUnreceivedMessagesFromOneSender": 20,
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
}
"InstanceUrl": "prod.enmeshed.eu",
"MaxNumberOfUnreceivedMessagesFromOneSender": 20,
"Pagination": {
"DefaultPageSize": 50,
"MaxPageSize": 200
}
}
},
"Relationships": {
Expand Down
Loading

0 comments on commit a06996b

Please sign in to comment.