mirror of
https://github.com/Govor-team/Govor.git
synced 2026-07-21 19:54:55 +00:00
Add cryptographic session key management for users
Introduces a new cryptographic key management system for user sessions, including models, DTOs, interfaces, and services for handling identity keys, signed pre-keys, and one-time pre-keys. Updates the SessionKeysController and DI configuration to use the new services. Adds related EF Core configurations and migrations, and updates the UserSession model to reference the new UserCryptoSession. Removes the obsolete ISessionKeyService interface.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.UserSession.Crypto;
|
||||
using Govor.Core.Models.Users.Crypto;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Govor.Application.Services.UserSessions.Crypto;
|
||||
|
||||
public class OneTimePreKeysRotator : IOneTimePreKeysRotator
|
||||
{
|
||||
private readonly ILogger<SessionKeyAttacher> _logger;
|
||||
private readonly ICurrentUserService _current;
|
||||
private readonly GovorDbContext _context;
|
||||
|
||||
public OneTimePreKeysRotator(ILogger<SessionKeyAttacher> logger, ICurrentUserService current, GovorDbContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_current = current;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task RotateOneTimePreKeysAsync(Guid sessionId, IEnumerable<byte[]> newOneTimePreKeys)
|
||||
{
|
||||
var cryptoSession = await _context.UserCryptoSessions
|
||||
.Include(c => c.OneTimePreKeys)
|
||||
.FirstOrDefaultAsync(c => c.UserSessionId == sessionId);
|
||||
|
||||
if (cryptoSession == null)
|
||||
throw new ArgumentException("Crypto session not found", nameof(sessionId));
|
||||
|
||||
// Удаляем все использованные ключи
|
||||
var usedKeys = cryptoSession.OneTimePreKeys.Where(k => k.IsUsed).ToList();
|
||||
if (usedKeys.Any())
|
||||
{
|
||||
_context.OneTimePreKeys.RemoveRange(usedKeys);
|
||||
}
|
||||
|
||||
// Добавляем новые ключи (возможно, стоит ограничить количество)
|
||||
foreach (var key in newOneTimePreKeys)
|
||||
{
|
||||
cryptoSession.OneTimePreKeys.Add(new OneTimePreKey
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PublicKey = key,
|
||||
IsUsed = false,
|
||||
UploadedAt = DateTime.UtcNow
|
||||
});
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
public async Task MarkOneTimePreKeyAsUsedAsync(Guid sessionId, Guid oneTimePreKeyId)
|
||||
{
|
||||
var key = await _context.OneTimePreKeys
|
||||
.Include(k => k.UserCryptoSession)
|
||||
.FirstOrDefaultAsync(k => k.Id == oneTimePreKeyId && k.UserCryptoSession.UserSessionId == sessionId);
|
||||
|
||||
if (key == null)
|
||||
throw new ArgumentException("One-Time PreKey not found for this session", nameof(oneTimePreKeyId));
|
||||
|
||||
if (key.IsUsed)
|
||||
return; // Уже помечен
|
||||
|
||||
key.IsUsed = true;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.UserSession.Crypto;
|
||||
using Govor.Core.Models.Users.Crypto;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Govor.Application.Services.UserSessions.Crypto;
|
||||
|
||||
public class SessionKeyAttacher : ISessionKeyAttacher
|
||||
{
|
||||
private readonly ILogger<SessionKeyAttacher> _logger;
|
||||
private readonly ICurrentUserService _current;
|
||||
private readonly GovorDbContext _context;
|
||||
|
||||
public SessionKeyAttacher(ILogger<SessionKeyAttacher> logger, ICurrentUserService current, GovorDbContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_current = current;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task AttachKeysAsync(
|
||||
Guid sessionId,
|
||||
byte[] identityKey,
|
||||
byte[] signedPreKey,
|
||||
byte[] signedPreKeySignature,
|
||||
IEnumerable<byte[]> oneTimePreKeys)
|
||||
{
|
||||
var session = await _context.UserSessions
|
||||
.Include(s => s.CryptoSession)
|
||||
.ThenInclude(c => c.OneTimePreKeys)
|
||||
.FirstOrDefaultAsync(s => s.Id == sessionId);
|
||||
|
||||
if (session == null)
|
||||
throw new ArgumentException("Session not found", nameof(sessionId));
|
||||
|
||||
if (session.CryptoSession != null)
|
||||
throw new InvalidOperationException("Keys are already attached to this session");
|
||||
|
||||
if(session.UserId != _current.GetCurrentUserId())
|
||||
throw new InvalidOperationException("You cannot attach keys to this session");
|
||||
|
||||
var cryptoSessionId = Guid.NewGuid();
|
||||
|
||||
var cryptoSession = new UserCryptoSession
|
||||
{
|
||||
Id = cryptoSessionId,
|
||||
UserSessionId = sessionId,
|
||||
PublicIdentityKey = identityKey,
|
||||
SignedPreKey = new SignedPreKey()
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserCryptoSessionId = cryptoSessionId,
|
||||
PublicSignedPreKey = signedPreKey,
|
||||
SignedPreKeySignature = signedPreKeySignature,
|
||||
},
|
||||
OneTimePreKeys = oneTimePreKeys.Select(key => new OneTimePreKey
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
PublicKey = key,
|
||||
IsUsed = false,
|
||||
UploadedAt = DateTime.UtcNow
|
||||
}).ToList()
|
||||
};
|
||||
|
||||
session.CryptoSession = cryptoSession;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Attached keys to session {SessionId}", sessionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using Govor.Application.Interfaces.Infrastructure.Extensions;
|
||||
using Govor.Application.Interfaces.UserSession.Crypto;
|
||||
using Govor.Core.Models.Users.Crypto;
|
||||
using Govor.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Govor.Application.Services.UserSessions.Crypto;
|
||||
|
||||
public class SessionKeysReader : ISessionKeysReader
|
||||
{
|
||||
private readonly ILogger<SessionKeyAttacher> _logger;
|
||||
private readonly GovorDbContext _context;
|
||||
|
||||
public SessionKeysReader(ILogger<SessionKeyAttacher> logger, GovorDbContext context)
|
||||
{
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<bool> HasKeysAttachedAsync(Guid sessionId)
|
||||
{
|
||||
return await _context.UserCryptoSessions.AnyAsync(c => c.UserSessionId == sessionId);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<UserCryptoSession>> GetAllActiveKeysAsync(Guid userId)
|
||||
{
|
||||
_logger.LogInformation("Getting all active keys for user {UserId}.", userId);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
return await _context.UserCryptoSessions
|
||||
.AsNoTracking()
|
||||
.Include(c => c.OneTimePreKeys)
|
||||
.Include(c => c.UserSession)
|
||||
.Where(c => c.UserSession.UserId == userId
|
||||
&& !c.UserSession.IsRevoked
|
||||
&& c.UserSession.ExpiresAt > now)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> GetRemainingOneTimePreKeysCountAsync(Guid sessionId)
|
||||
{
|
||||
_logger.LogInformation("Getting count of one time pre keys for session {UserId}.", sessionId);
|
||||
|
||||
return await _context.OneTimePreKeys
|
||||
.AsNoTracking()
|
||||
.Include(f => f.UserCryptoSession)
|
||||
.CountAsync(f => f.UserCryptoSession.UserSessionId == sessionId);
|
||||
}
|
||||
|
||||
public async Task<UserCryptoSession?> GetKeysBySessionIdAsync(Guid sessionId)
|
||||
{
|
||||
_logger.LogInformation("Getting keys for session {session}.", sessionId);
|
||||
|
||||
return await _context.UserCryptoSessions
|
||||
.AsNoTracking()
|
||||
.Include(c => c.OneTimePreKeys)
|
||||
.Include(c => c.UserSession)
|
||||
.FirstOrDefaultAsync(c => c.UserSessionId == sessionId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user