wip passkey registration
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using Fido2NetLib.Objects;
|
||||||
|
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
using MongoDB.Bson.Serialization.Attributes;
|
using MongoDB.Bson.Serialization.Attributes;
|
||||||
@@ -19,6 +21,7 @@ public class User
|
|||||||
public bool IsArgon { get; set; }
|
public bool IsArgon { get; set; }
|
||||||
public ObjectId[] ApiKeys { get; set; } = [];
|
public ObjectId[] ApiKeys { get; set; } = [];
|
||||||
public List<ObjectId> RegTokens { get; set; } = [];
|
public List<ObjectId> RegTokens { get; set; } = [];
|
||||||
|
public List<PublicKeyCredentialDescriptor> CredentialDescriptors { get; set; } = [];
|
||||||
|
|
||||||
public ClaimsIdentity GetIdentity()
|
public ClaimsIdentity GetIdentity()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using AobaCore.Models;
|
using AobaCore.Models;
|
||||||
|
|
||||||
|
using Fido2NetLib.Objects;
|
||||||
|
|
||||||
using Isopoh.Cryptography.Argon2;
|
using Isopoh.Cryptography.Argon2;
|
||||||
|
|
||||||
using MongoDB.Bson;
|
using MongoDB.Bson;
|
||||||
@@ -54,13 +56,18 @@ public class AccountsService(IMongoDatabase db)
|
|||||||
/* Get the salt */
|
/* Get the salt */
|
||||||
byte[] salt = new byte[16];
|
byte[] salt = new byte[16];
|
||||||
Array.Copy(hashBytes, 0, salt, 0, 16);
|
Array.Copy(hashBytes, 0, salt, 0, 16);
|
||||||
/* Compute the hash on the password the user entered */
|
|
||||||
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000, HashAlgorithmName.SHA1);
|
var hash= Rfc2898DeriveBytes.Pbkdf2(password, salt, 10000, HashAlgorithmName.SHA1, 20);
|
||||||
byte[] hash = pbkdf2.GetBytes(20);
|
|
||||||
/* Compare the results */
|
/* Compare the results */
|
||||||
for (int i = 0; i < 20; i++)
|
for (int i = 0; i < 20; i++)
|
||||||
if (hashBytes[i + 16] != hash[i])
|
if (hashBytes[i + 16] != hash[i])
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<PublicKeyCredentialDescriptor>> GetPublicKeyCredentialDescriptorsAsync(ObjectId id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var creds = await _users.Find(u => u.Id == id).Project(u => u.CredentialDescriptors).FirstOrDefaultAsync(cancellationToken);
|
||||||
|
return creds ?? [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,16 @@ builder.Services.AddAuthentication(options =>
|
|||||||
|
|
||||||
|
|
||||||
builder.Services.AddAoba();
|
builder.Services.AddAoba();
|
||||||
|
builder.Services.AddFido2(opts =>
|
||||||
|
{
|
||||||
|
opts.ServerName = "Aoba";
|
||||||
|
opts.ServerDomain = "aoba.app";
|
||||||
|
#if DEBUG
|
||||||
|
opts.Origins = new HashSet<string> { "http://localhost:8081", "http://127.0.0.1:8080" };
|
||||||
|
#else
|
||||||
|
opts.Origins = new HashSet<string> { "https://aoba.app" };
|
||||||
|
#endif
|
||||||
|
});
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
builder.Services.AddHostedService<DebugService>();
|
builder.Services.AddHostedService<DebugService>();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import "google/protobuf/empty.proto";
|
|||||||
import "Proto/Types.proto";
|
import "Proto/Types.proto";
|
||||||
|
|
||||||
service AccountRpc {
|
service AccountRpc {
|
||||||
rpc RegisterPasskey(google.protobuf.Empty) returns (PasskeyRegistrationCreds);
|
rpc RegisterPasskey(google.protobuf.Empty) returns (PasskeyCredentialCreateOptions);
|
||||||
rpc CompletePasskeyRegistration(PasskeyPublicKey) returns (google.protobuf.Empty);
|
rpc CompletePasskeyRegistration(PasskeyRegistrationCredentials) returns (google.protobuf.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,9 +121,19 @@ message PasskeyPayload {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message PasskeyRegistrationCreds{
|
message PasskeyCredentialCreateOptions{
|
||||||
|
string challenge = 1;
|
||||||
|
string userId = 2;
|
||||||
|
}
|
||||||
|
message PasskeyRegistrationCredentials{
|
||||||
|
string id = 1;
|
||||||
|
string rawId = 2;
|
||||||
|
string type = 3;
|
||||||
|
CredentialsClientResponse response = 4;
|
||||||
}
|
}
|
||||||
message PasskeyPublicKey{
|
|
||||||
|
|
||||||
|
message CredentialsClientResponse{
|
||||||
|
string clientDataJSON = 1;
|
||||||
|
string attestationObject = 2;
|
||||||
|
string authenticatorData = 3;
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,47 @@
|
|||||||
using Aoba.RPC;
|
using Aoba.RPC;
|
||||||
using Aoba.RPC.Account;
|
using Aoba.RPC.Account;
|
||||||
|
|
||||||
|
using AobaCore.Services;
|
||||||
|
|
||||||
|
using AobaServer.Utils;
|
||||||
|
|
||||||
|
using Fido2NetLib;
|
||||||
|
|
||||||
using Google.Protobuf.WellKnownTypes;
|
using Google.Protobuf.WellKnownTypes;
|
||||||
|
|
||||||
using Grpc.Core;
|
using Grpc.Core;
|
||||||
|
|
||||||
|
using Isopoh.Cryptography.Argon2;
|
||||||
|
|
||||||
namespace AobaServer.Services;
|
namespace AobaServer.Services;
|
||||||
|
|
||||||
public class AccountRpcService : AccountRpc.AccountRpcBase
|
public class AccountRpcService(IFido2 fido2, AccountsService accounts) : AccountRpc.AccountRpcBase
|
||||||
{
|
{
|
||||||
public override Task<PasskeyRegistrationCreds> RegisterPasskey(Empty request, ServerCallContext context)
|
public override async Task<PasskeyCredentialCreateOptions> RegisterPasskey(Empty request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return base.RegisterPasskey(request, context);
|
var curUser = await accounts.GetUserAsync(context.GetUserId(), context.CancellationToken);
|
||||||
|
if (curUser == null)
|
||||||
|
throw new Exception($"Logged in user does not exist somehow. Id: {context.GetUserId()}");
|
||||||
|
var user = new Fido2User
|
||||||
|
{
|
||||||
|
DisplayName = curUser.Username,
|
||||||
|
Id = curUser.Id.ToByteArray(),
|
||||||
|
Name = curUser.Username
|
||||||
|
};
|
||||||
|
|
||||||
|
var credOptions = fido2.RequestNewCredential(new RequestNewCredentialParams
|
||||||
|
{
|
||||||
|
User = user,
|
||||||
|
ExcludeCredentials = curUser.CredentialDescriptors
|
||||||
|
});
|
||||||
|
return new PasskeyCredentialCreateOptions
|
||||||
|
{
|
||||||
|
Challenge = credOptions.Challenge.ToB64String().Replace('+', '-').Replace('/', '_'),
|
||||||
|
UserId = credOptions.User.Id.ToB64String().Replace('+', '-').Replace('/', '_')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<Empty> CompletePasskeyRegistration(PasskeyPublicKey request, ServerCallContext context)
|
public override Task<Empty> CompletePasskeyRegistration(PasskeyRegistrationCredentials request, ServerCallContext context)
|
||||||
{
|
{
|
||||||
return base.CompletePasskeyRegistration(request, context);
|
return base.CompletePasskeyRegistration(request, context);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user