Skip to content
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

feat(auth): implement did:key resolution #90

Merged
merged 9 commits into from
Jan 4, 2025
Merged
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
2 changes: 2 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ trim_trailing_whitespace = true
[*.{cs|csx}]
indent_style = tab
max_line_length = 88
# enforce naming rules
dotnet_diagnostic.IDE1006.severity = warning
35 changes: 35 additions & 0 deletions .github/workflows/compilation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,38 @@ jobs:
${{ env.RELEASE_PREFIX }}-Client.zip
${{ env.RELEASE_PREFIX }}-Server.zip

dotnet-build:
name: Run `dotnet build`
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: "Checkout repository"
timeout-minutes: 2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Setup dotnet"
timeout-minutes: 2
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: dotnet build
run: dotnet build

dotnet-test:
name: Run `dotnet test`
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: "Checkout repository"
timeout-minutes: 2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: "Setup dotnet"
timeout-minutes: 2
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: dotnet test
run: dotnet test --logger:"console;verbosity=detailed"
4 changes: 2 additions & 2 deletions Basis Server/BasisPrometheus/BasisPrometheus.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard2.1;net9.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
Expand Down
81 changes: 81 additions & 0 deletions Basis.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Contrib", "Contrib", "{2736B04F-892B-4412-8A6F-90EDD68DFE94}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Auth", "Auth", "{7315D5F3-E752-4246-A876-6FC975262B3B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Did", "Contrib\Auth\Did\Did.csproj", "{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Basis Server", "Basis Server", "{153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasisNetworkConsole", "Basis Server\BasisServerConsole\BasisNetworkConsole.csproj", "{712C18D2-47A3-4F21-B61D-97CABDB8134C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasisNetworkClient", "Basis Server\BasisNetworkClient\BasisNetworkClient.csproj", "{C7374CCE-9D17-46A6-86E6-61812A7C52FF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasisNetworkCore", "Basis Server\BasisNetworkCore\BasisNetworkCore.csproj", "{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasisNetworkServer", "Basis Server\BasisNetworkServer\BasisNetworkServer.csproj", "{5A7487D9-3DE5-4A0C-8A55-AF82824E0588}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Contrib\Auth\Did.Tests\Tests.csproj", "{4266CEAF-4D05-4677-B775-BF2DC94E3477}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BasisPrometheus", "Basis Server\BasisPrometheus\BasisPrometheus.csproj", "{70939FBF-C02E-488D-8C6E-04B04EFEF326}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiteNetLib", "Basis Server\LiteNetLib\LiteNetLib.csproj", "{BE757547-04D5-425A-A06C-747E0DBB095F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED}.Release|Any CPU.Build.0 = Release|Any CPU
{712C18D2-47A3-4F21-B61D-97CABDB8134C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{712C18D2-47A3-4F21-B61D-97CABDB8134C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{712C18D2-47A3-4F21-B61D-97CABDB8134C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{712C18D2-47A3-4F21-B61D-97CABDB8134C}.Release|Any CPU.Build.0 = Release|Any CPU
{C7374CCE-9D17-46A6-86E6-61812A7C52FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7374CCE-9D17-46A6-86E6-61812A7C52FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7374CCE-9D17-46A6-86E6-61812A7C52FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7374CCE-9D17-46A6-86E6-61812A7C52FF}.Release|Any CPU.Build.0 = Release|Any CPU
{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898}.Release|Any CPU.Build.0 = Release|Any CPU
{5A7487D9-3DE5-4A0C-8A55-AF82824E0588}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5A7487D9-3DE5-4A0C-8A55-AF82824E0588}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5A7487D9-3DE5-4A0C-8A55-AF82824E0588}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5A7487D9-3DE5-4A0C-8A55-AF82824E0588}.Release|Any CPU.Build.0 = Release|Any CPU
{4266CEAF-4D05-4677-B775-BF2DC94E3477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4266CEAF-4D05-4677-B775-BF2DC94E3477}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4266CEAF-4D05-4677-B775-BF2DC94E3477}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4266CEAF-4D05-4677-B775-BF2DC94E3477}.Release|Any CPU.Build.0 = Release|Any CPU
{70939FBF-C02E-488D-8C6E-04B04EFEF326}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{70939FBF-C02E-488D-8C6E-04B04EFEF326}.Debug|Any CPU.Build.0 = Debug|Any CPU
{70939FBF-C02E-488D-8C6E-04B04EFEF326}.Release|Any CPU.ActiveCfg = Release|Any CPU
{70939FBF-C02E-488D-8C6E-04B04EFEF326}.Release|Any CPU.Build.0 = Release|Any CPU
{BE757547-04D5-425A-A06C-747E0DBB095F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BE757547-04D5-425A-A06C-747E0DBB095F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BE757547-04D5-425A-A06C-747E0DBB095F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BE757547-04D5-425A-A06C-747E0DBB095F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7315D5F3-E752-4246-A876-6FC975262B3B} = {2736B04F-892B-4412-8A6F-90EDD68DFE94}
{8361FAB8-8C1C-4F14-A9B1-5B9A98D086ED} = {7315D5F3-E752-4246-A876-6FC975262B3B}
{712C18D2-47A3-4F21-B61D-97CABDB8134C} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
{C7374CCE-9D17-46A6-86E6-61812A7C52FF} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
{22DCA720-92E8-4AD4-8D9A-2BD4CF4BE898} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
{5A7487D9-3DE5-4A0C-8A55-AF82824E0588} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
{4266CEAF-4D05-4677-B775-BF2DC94E3477} = {7315D5F3-E752-4246-A876-6FC975262B3B}
{70939FBF-C02E-488D-8C6E-04B04EFEF326} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
{BE757547-04D5-425A-A06C-747E0DBB095F} = {153BDCB8-6E62-4BB4-A8FD-DD4B6A8FB0AE}
EndGlobalSection
EndGlobal
53 changes: 53 additions & 0 deletions Contrib/Auth/Did.Tests/DidKeyTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using Basis.Contrib.Auth.DecentralizedIds.Newtypes;
using Xunit;
using JsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey;

namespace Basis.Contrib.Auth.DecentralizedIds
{
public class DidKeyTests
{
[Fact]
public async Task DidKeyTestVectors()
{
// See https://w3c-ccg.github.io/did-method-key/#ed25519-x25519
var examples = new List<(string, JsonWebKey)>
{
(
"did:key:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp",
new JsonWebKey()
{
Kty = "OKP",
Crv = "Ed25519",
X = "O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
}
),
};

var resolver = new DidKeyResolver();
foreach (var (inputDid, expectedJwk) in examples)
{
var expectedFragment = new DidUrlFragment(
inputDid.Split(
DidKeyResolver.PREFIX,
count: 2,
options: StringSplitOptions.RemoveEmptyEntries
)[0]
);
var document = await resolver.ResolveDocument(new Did(inputDid));
Debug.Assert(document.Pubkeys.Count == 1);
var resolvedJwk = document.Pubkeys[expectedFragment];
Debug.Assert(
JsonSerializer.Serialize(resolvedJwk)
== JsonSerializer.Serialize(expectedJwk),
"resolved JWK did not match expected JWK"
);
}
}
}
}
23 changes: 23 additions & 0 deletions Contrib/Auth/Did.Tests/Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--Language settings-->
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>9.0</LangVersion>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
</PropertyGroup>

<!--Third-party dependencies-->
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
<PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>

<!--First-party dependencies-->
<ItemGroup>
<ProjectReference Include="..\Did\Did.csproj" />
</ItemGroup>

</Project>
15 changes: 14 additions & 1 deletion Contrib/Auth/Did/Did.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<!--Language Settings-->
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<LangVersion>9.0</LangVersion>

<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
</PropertyGroup>

<!--Ignore Subproject dirs-->
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);Tests\**</DefaultItemExcludes>
</PropertyGroup>

<!--Third-party dependencies-->
<ItemGroup>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.9.0" />
<PackageReference Include="SimpleBase" Version="4.0.2" />
<PackageReference Include="VarInt" Version="1.2.2" />
</ItemGroup>
</Project>
97 changes: 94 additions & 3 deletions Contrib/Auth/Did/DidAuth.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,96 @@
namespace Basis.Contrib.Auth.Did
using System;
using Basis.Contrib.Auth.DecentralizedIds.Newtypes;
using CryptoRng = System.Security.Cryptography.RandomNumberGenerator;

namespace Basis.Contrib.Auth.DecentralizedIds
{
// TODO: Create and implement an `IChallengeResponseAuth` interface. This interface should live in basis core.
public class DidAuthentication { }
/// Configuration for [`DidAuthentication`].
public record Config
{
public CryptoRng Rng { get; init; } = CryptoRng.Create();
}

// TODO(@thebutlah): Create and implement an `IChallengeResponseAuth`
// interface. This interface should live in basis core.
public class DidAuthentication
{
/// Number of bytes in a nonce. This is currently 256 bits.
// TODO(@thebutlah): Decide if its too performance intensive to use 256
// bits, and if 128 bit would be sufficient.
const ushort NONCE_LEN = 256 / sizeof(byte);

// We store the rng to make deterministic testing and seeding possible.
readonly CryptoRng Rng;

public DidAuthentication(Config cfg)
{
Rng = cfg.Rng;
}

public Challenge MakeChallenge(Did identity)
{
var nonce = new byte[NONCE_LEN];
Rng.GetBytes(nonce);
return new Challenge(Identity: identity, Nonce: nonce);
}

/// Compares the response against the original challenge.
///
/// Ensures that:
/// * The response signature matches the public keys of the challenge
/// identity.
/// * The response signature payload matches the nonce in the challenge
///
/// It is the caller's responsibility to keep track of which challenges
/// should be held for which responses.
public VerifyResponseResult VerifyResponse(
Response response,
Challenge challenge
)
{
throw new NotImplementedException("todo");
}
}

/// Challenges are a randomized nonce. The nonce will be the payload
/// that is signed by the user's private key. Generating a random nonce
/// for every authentication attempt ensures that an attacker cannot
/// perform a [replay attack](https://en.wikipedia.org/wiki/Replay_attack).
///
/// Challenges also track the identity of the party that the challenge was
/// sent to, so that later the signature's public key can be compared to
/// the identity's public key.
public record Challenge(Did Identity, byte[] Nonce);

public record Response(
/// A JSON Web Signature, in "compact serialization" form. The payload
/// of the JWS are the bytes of the corresponding challenge's nonce.
JwsCompact Jws,
/// The particular key in the user's did document. If the empty string,
/// it is implied that there is only one key in the document and that
/// this single key should be what is used as the pub key.
///
/// Examples:
/// * `""`
/// * `"key-0"`
/// * `"z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"`
DidUrlFragment DidUrlFragment
);

/// Possible return values VerifyResponse method.
public enum VerifyResponseResult
{
/// The verification was successful
Success,

/// The fragment in the response didn't exist in the DID Document resolved
/// from the challenge's identity.
NoSuchFragment,

/// The JWS Payload did not match the challenge nonce
MismatchedNonce,

/// The JWS verification failed due to an invalid signature
InvalidSig,
}
}
11 changes: 11 additions & 0 deletions Contrib/Auth/Did/DidDocument.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System.Collections.ObjectModel;
using DidUrlFragment = Basis.Contrib.Auth.DecentralizedIds.Newtypes.DidUrlFragment;
using JsonWebKey = Microsoft.IdentityModel.Tokens.JsonWebKey;

namespace Basis.Contrib.Auth.DecentralizedIds
{
/// Contains the info that we care about in the DID Document.
/// A DID Document is what a DID is resolved into. See
/// https://www.w3.org/TR/did-core/#did-resolution
public record DidDocument(ReadOnlyDictionary<DidUrlFragment, JsonWebKey> Pubkeys);
}
Loading
Loading