Skip to content

Commit

Permalink
tmp
Browse files Browse the repository at this point in the history
  • Loading branch information
TheButlah committed Feb 23, 2025
1 parent 54e21a5 commit cdbff20
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 24 deletions.
2 changes: 1 addition & 1 deletion Contrib/Auth/Did/Base64UrlSafe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Basis.Contrib.Auth.DecentralizedIds
{
/// Base64 url-safe encode and decode.
public class Base64UrlSafe
public sealed class Base64UrlSafe
{
public static string Encode(byte[] bytes)
{
Expand Down
120 changes: 109 additions & 11 deletions Contrib/Auth/Did/DidAuth.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
#nullable enable

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Basis.Contrib.Auth.DecentralizedIds.Newtypes;
using CryptoRng = System.Security.Cryptography.RandomNumberGenerator;
using Debug = System.Diagnostics.Debug;

namespace Basis.Contrib.Auth.DecentralizedIds
{
/// Configuration for [`DidAuthentication`].
public record Config
public sealed record Config
{
public CryptoRng Rng { get; init; } = CryptoRng.Create();
public IDictionary<DidMethodKind, IDidMethod> Resolvers { get; init; } =
new Dictionary<DidMethodKind, IDidMethod>()
{
// We will add more did methods in the future, like did:web
{ DidMethodKind.Key, new DidKeyResolver() },
};
}

// TODO(@thebutlah): Create and implement an `IChallengeResponseAuth`
// interface. This interface should live in basis core.
public class DidAuthentication
public sealed class DidAuthentication
{
/// Number of bytes in a nonce. This is currently 256 bits.
// TODO(@thebutlah): Decide if its too performance intensive to use 256
Expand All @@ -24,16 +35,20 @@ public class DidAuthentication
// We store the rng to make deterministic testing and seeding possible.
readonly CryptoRng Rng;

// We support possibly multiple did resolvers.
readonly IDictionary<DidMethodKind, IDidMethod> Resolvers;

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

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

/// Compares the response against the original challenge.
Expand All @@ -45,12 +60,93 @@ public Challenge MakeChallenge(Did identity)
///
/// It is the caller's responsibility to keep track of which challenges
/// should be held for which responses.
public VerifyResponseResult VerifyResponse(
public async Task<VerifyResponseResult> VerifyResponse(
Response response,
Challenge challenge
)
{
throw new NotImplementedException("todo");
var document = await ResolveDid(challenge.Identity);
var pubkey = RetrieveKey(document, response.DidUrlFragment);
var (isVerified, verifySigErr) = VerifySignature(
pubkey,
challenge.Nonce,
response.Signature
);
if (!isVerified)
{
return VerifyResponseResult.Success;
}
return VerifyResponseResult.Success;
}

private (bool, DidSignatureErr?) VerifySignature(
JsonWebKey pubkey,
Nonce nonce,
Signature signature
)
{
throw new NotImplementedException("todo: do the cryptography");
}

private JsonWebKey RetrieveKey(DidDocument document, DidUrlFragment keyId)
{
JsonWebKey pubkeyJwk;
if (keyId.V.Equals(string.Empty))
{
if (document.Pubkeys.Count != 1)
{
throw new DidResolveException(DidResolveErr.AmbiguousFragment);
}
pubkeyJwk = document.Pubkeys.First().Value;
}
if (!document.Pubkeys.TryGetValue(keyId, out pubkeyJwk))
{
throw new DidResolveException(DidResolveErr.NoSuchFragment);
}
return pubkeyJwk;
}

private async Task<DidDocument> ResolveDid(Did identity)
{
var segments = identity.V.Split(
separator: ":",
count: 3,
StringSplitOptions.None
);
if (segments.Length != 3 || segments[0] != "did")
{
throw new DidResolveException(DidResolveErr.InvalidPrefix);
}
var method = segments[1] switch
{
"key" => DidMethodKind.Key,
_ => throw new DidResolveException(DidResolveErr.UnsupportedMethod),
};
var resolver = Resolvers[method];
return await resolver.ResolveDocument(identity);
}

/// Errors related to validating to the signature itself.
public enum DidSignatureErr { }

/// Errors related to resolving a Did document from a Did.
public enum DidResolveErr
{
InvalidPrefix,
UnsupportedMethod,
NoSuchFragment,
AmbiguousFragment,
}

public sealed class DidResolveException : Exception
{
public DidResolveErr Error { get; }

public DidResolveException(DidResolveErr error)
: base(error.ToString())
{
Error = error;
}
}
}

Expand All @@ -62,12 +158,11 @@ Challenge challenge
/// 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 Challenge(Did Identity, Nonce 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 raw bytes of the signature. For ed25519 this is 64 bytes long.
Signature Signature,
/// 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.
Expand All @@ -85,14 +180,17 @@ public enum VerifyResponseResult
/// The verification was successful
Success,

/// Was unable to resolve the Did to a DidDocument.
FailedToResolveDid,

/// 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
/// The response signature did not match the challenge nonce
MismatchedNonce,

/// The JWS verification failed due to an invalid signature
/// The verification failed due to an invalid signature
InvalidSig,
}
}
4 changes: 3 additions & 1 deletion Contrib/Auth/Did/DidDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ 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);
public sealed record DidDocument(
ReadOnlyDictionary<DidUrlFragment, JsonWebKey> Pubkeys
);
}
6 changes: 4 additions & 2 deletions Contrib/Auth/Did/DidKeyResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace Basis.Contrib.Auth.DecentralizedIds
{
/// Implements resolution of a did:key to the various information stored in it
public class DidKeyResolver : IDidMethod
public sealed class DidKeyResolver : IDidMethod
{
public const string PREFIX = "did:key:";

Expand All @@ -24,6 +24,8 @@ public class DidKeyResolver : IDidMethod
/// https://datatracker.ietf.org/doc/html/draft-multiformats-multibase#appendix-D.1
const char BASE58_BTC_MULTIBASE_CODE = 'z';

public DidMethodKind Kind => DidMethodKind.Key;

public Task<DidDocument> ResolveDocument(Did did)
{
// Task is immediately complete, we don't need any io.
Expand Down Expand Up @@ -112,7 +114,7 @@ public enum DidKeyDecodeError
WrongPubkeyLen,
}

public class DidKeyDecodeException : System.Exception
public sealed class DidKeyDecodeException : System.Exception
{
public DidKeyDecodeError Error { get; }

Expand Down
12 changes: 10 additions & 2 deletions Contrib/Auth/Did/IDidMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

using System.Threading.Tasks;
using Did = Basis.Contrib.Auth.DecentralizedIds.Newtypes.Did;
using DidDocument = Basis.Contrib.Auth.DecentralizedIds.DidDocument;

namespace Basis.Contrib.Auth.DecentralizedIds
{
/// The functionality that all DID methods implement
interface IDidMethod
public interface IDidMethod
{
/// Resolves to a map of DID Url fragments to a Json Web Key. This method
/// resolves a DID to its DID Document, and inspects the `verificationMethods`
Expand All @@ -17,5 +16,14 @@ interface IDidMethod
/// we standardize the api to return JsonWebKey because it documents its
/// own key algorithms and is a reasonably portable format.
public Task<DidDocument> ResolveDocument(Did did);

public DidMethodKind Kind { get; }
}

/// The different supported DidMethods.
public enum DidMethodKind
{
Key,
// TODO: Web
}
}
2 changes: 1 addition & 1 deletion Contrib/Auth/Did/JsonWebKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Basis.Contrib.Auth.DecentralizedIds
{
public class JsonWebKey
public sealed class JsonWebKey
{
[JsonProperty("kty")]
public string? Kty { get; set; }
Expand Down
15 changes: 9 additions & 6 deletions Contrib/Auth/Did/Newtypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,19 @@ namespace Basis.Contrib.Auth.DecentralizedIds.Newtypes
{
/// A DID. DIDs do *not* contain any fragment portion. See
/// https://www.w3.org/TR/did-core/#did-syntax
public record Did(string V);
public sealed record Did(string V);

/// A full DID Url, which is a did along with an optional path query and
/// fragment. See
/// https://www.w3.org/TR/did-core/#did-url-syntax
public record DidUrl(string V);
public sealed record DidUrl(string V);

/// A DID Url Fragment. Does not include the `#` part.
public record DidUrlFragment(string V);
/// A DID Url Fragment. Does not include the `#` part. Can be empty.
public sealed record DidUrlFragment(string V);

/// A JSON Web Signature, serialized in "compact serialization" form.
public record JwsCompact(string V);
/// A signature.
public sealed record Signature(byte[] V);

/// A random nonce.
public sealed record Nonce(byte[] V);
}

0 comments on commit cdbff20

Please sign in to comment.