1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Generate fake credentials on login

This commit is contained in:
Bernd Schoolmann 2025-03-19 10:49:17 +01:00
parent 6f2355c4bc
commit 6a2aab5616
No known key found for this signature in database
4 changed files with 107 additions and 27 deletions

View File

@ -13,7 +13,7 @@ public class OpaqueRegistrationStartRequest
public class CipherConfiguration
{
static string OpaqueKe3Ristretto3DHArgonSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF";
public static string OpaqueKe3Ristretto3DHArgonSuite = "OPAQUE_3_RISTRETTO255_OPRF_RISTRETTO255_KEGROUP_3DH_KEX_ARGON2ID13_KSF";
[Required]
public string CipherSuite { get; set; }
@ -26,6 +26,7 @@ public class CipherConfiguration
{
return new Bitwarden.Opaque.CipherConfiguration
{
OpaqueVersion = 3,
OprfCs = Bitwarden.Opaque.OprfCs.Ristretto255,
KeGroup = Bitwarden.Opaque.KeGroup.Ristretto255,
KeyExchange = Bitwarden.Opaque.KeyExchange.TripleDH,

View File

@ -1,4 +1,5 @@
using System.Text;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Api.Request.Opaque;
@ -7,6 +8,8 @@ using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
using Bit.Core.Entities;
using Bit.Core.Repositories;
using Bit.Core.Settings;
using Bit.Core.Utilities;
using Bitwarden.Opaque;
using Microsoft.Extensions.Caching.Distributed;
@ -20,6 +23,7 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
private readonly IOpaqueKeyExchangeCredentialRepository _opaqueKeyExchangeCredentialRepository;
private readonly IDistributedCache _distributedCache;
private readonly IUserRepository _userRepository;
private readonly byte[]? _defaultKdfHmacKey;
const string REGISTER_SESSION_KEY = "opaque_register_session_{0}";
const string LOGIN_SESSION_KEY = "opaque_login_session_{0}";
@ -27,13 +31,22 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
public OpaqueKeyExchangeService(
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository,
IDistributedCache distributedCache,
IUserRepository userRepository
IUserRepository userRepository,
GlobalSettings globalSettings
)
{
_bitwardenOpaque = new BitwardenOpaqueServer();
_opaqueKeyExchangeCredentialRepository = opaqueKeyExchangeCredentialRepository;
_distributedCache = distributedCache;
_userRepository = userRepository;
if (CoreHelpers.SettingHasValue(globalSettings.KdfDefaultHashKey))
{
_defaultKdfHmacKey = Encoding.UTF8.GetBytes(globalSettings.KdfDefaultHashKey);
}
else
{
_defaultKdfHmacKey = new byte[32];
}
}
public async Task<OpaqueRegistrationStartResponse> StartRegistration(byte[] request, User user, Models.Api.Request.Opaque.CipherConfiguration cipherConfiguration)
@ -75,21 +88,38 @@ public class OpaqueKeyExchangeService : IOpaqueKeyExchangeService
var user = await _userRepository.GetByEmailAsync(email);
if (user == null)
{
// todo don't allow user enumeration
throw new InvalidOperationException("User not found");
user = new User();
user.Id = Guid.Empty;
}
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
byte[]? serverSetup = null;
byte[]? serverRegistration = null;
Models.Api.Request.Opaque.CipherConfiguration? cipherConfiguration = null;
if (credential == null)
{
// generate fake credential
throw new InvalidOperationException("Credential not found");
using var hmac = new HMACSHA256(_defaultKdfHmacKey);
var hmacHash = hmac.ComputeHash(Encoding.ASCII.GetBytes(email));
(serverSetup, serverRegistration) = _bitwardenOpaque.SeededFakeRegistration(hmacHash);
cipherConfiguration = new Models.Api.Request.Opaque.CipherConfiguration
{
CipherSuite = Models.Api.Request.Opaque.CipherConfiguration.OpaqueKe3Ristretto3DHArgonSuite,
Argon2Parameters = new Argon2KsfParameters
{
Memory = 0,
Iterations = 0,
Parallelism = 0
}
};
}
else
{
var credentialBlob = JsonSerializer.Deserialize<OpaqueKeyExchangeCredentialBlob>(credential.CredentialBlob)!;
serverSetup = credentialBlob.ServerSetup;
serverRegistration = credentialBlob.PasswordFile;
cipherConfiguration = JsonSerializer.Deserialize<Models.Api.Request.Opaque.CipherConfiguration>(credential.CipherConfiguration)!;
}
var cipherConfiguration = JsonSerializer.Deserialize<Models.Api.Request.Opaque.CipherConfiguration>(credential.CipherConfiguration)!;
var credentialBlob = JsonSerializer.Deserialize<OpaqueKeyExchangeCredentialBlob>(credential.CredentialBlob)!;
var serverSetup = credentialBlob.ServerSetup;
var serverRegistration = credentialBlob.PasswordFile;
var loginResponse = _bitwardenOpaque.StartLogin(cipherConfiguration.ToNativeConfiguration(), serverSetup, serverRegistration, request, user.Id.ToString());
var sessionId = Guid.NewGuid();

View File

@ -32,7 +32,6 @@
<PackageReference Include="Azure.Storage.Blobs" Version="12.21.2" />
<PackageReference Include="Azure.Storage.Queues" Version="12.19.1" />
<PackageReference Include="BitPay.Light" Version="1.0.1907" />
<PackageReference Include="Bitwarden.Opaque" Version="0.0.1-beta.1" />
<PackageReference Include="DuoUniversal" Version="1.2.5" />
<PackageReference Include="DnsClient" Version="1.8.0" />
<PackageReference Include="Fido2.AspNet" Version="3.0.1" />
@ -79,9 +78,9 @@
<Folder Include="Properties\" />
</ItemGroup>
<!--
<ItemGroup>
<ProjectReference Include="../../../dotnet-extensions/extensions/Bitwarden.Opaque/src/Bitwarden.Opaque.csproj" />
</ItemGroup>
-->
</Project>

View File

@ -82,7 +82,7 @@ public class AccountsController : Controller
KdfIterations = AuthConstants.ARGON2_ITERATIONS.Default,
KdfMemory = AuthConstants.ARGON2_MEMORY.Default,
KdfParallelism = AuthConstants.ARGON2_PARALLELISM.Default,
}
},
];
public AccountsController(
@ -97,8 +97,8 @@ public class AccountsController : Controller
IReferenceEventService referenceEventService,
IFeatureService featureService,
IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable> registrationEmailVerificationTokenDataFactory,
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository,
GlobalSettings globalSettings
GlobalSettings globalSettings,
IOpaqueKeyExchangeCredentialRepository opaqueKeyExchangeCredentialRepository
)
{
_currentContext = currentContext;
@ -261,21 +261,21 @@ public class AccountsController : Controller
public async Task<PreloginResponseModel> PostPrelogin([FromBody] PreloginRequestModel model)
{
var kdfInformation = await _userRepository.GetKdfInformationByEmailAsync(model.Email);
var user = await _userRepository.GetByEmailAsync(model.Email);
if (kdfInformation == null || user == null)
CipherConfiguration cipherConfiguration = null;
if (kdfInformation == null)
{
kdfInformation = GetDefaultKdf(model.Email);
}
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
if (credential != null)
{
return new PreloginResponseModel(kdfInformation, JsonSerializer.Deserialize<CipherConfiguration>(credential.CipherConfiguration)!);
cipherConfiguration = GetDefaultOpaqueCipherConfig(model.Email, kdfInformation);
}
else
{
return new PreloginResponseModel(kdfInformation, null);
var user = await _userRepository.GetByEmailAsync(model.Email);
var credential = await _opaqueKeyExchangeCredentialRepository.GetByUserIdAsync(user.Id);
cipherConfiguration = JsonSerializer.Deserialize<CipherConfiguration>(credential.CipherConfiguration);
}
return new PreloginResponseModel(kdfInformation, cipherConfiguration);
}
[HttpGet("webauthn/assertion-options")]
@ -314,4 +314,54 @@ public class AccountsController : Controller
return _defaultKdfResults[hashIndex];
}
}
private CipherConfiguration GetDefaultOpaqueCipherConfig(string email, UserKdfInformation kdfInformation)
{
if (_defaultKdfHmacKey == null)
{
return null;
}
else
{
var hmacMessage = Encoding.UTF8.GetBytes(email.Trim().ToLowerInvariant());
using var hmac = new System.Security.Cryptography.HMACSHA256(_defaultKdfHmacKey);
var hmacHash = hmac.ComputeHash(hmacMessage);
var hashHex = BitConverter.ToString(hmacHash).Replace("-", string.Empty).ToLowerInvariant();
var hashFirst8Bytes = hashHex.Substring(16, 16);
var hashNumber = long.Parse(hashFirst8Bytes, System.Globalization.NumberStyles.HexNumber);
if ((int)(Math.Abs(hashNumber) % 10) == 0)
{
if (kdfInformation.Kdf == KdfType.Argon2id)
{
return new CipherConfiguration
{
CipherSuite = CipherConfiguration.OpaqueKe3Ristretto3DHArgonSuite,
Argon2Parameters = new Argon2KsfParameters
{
Memory = kdfInformation.KdfMemory.Value * 1024,
Iterations = kdfInformation.KdfIterations,
Parallelism = kdfInformation.KdfParallelism.Value
}
};
}
else
{
return new CipherConfiguration
{
CipherSuite = CipherConfiguration.OpaqueKe3Ristretto3DHArgonSuite,
Argon2Parameters = new Argon2KsfParameters
{
Memory = 256 * 1024,
Iterations = 1,
Parallelism = 4,
}
};
}
}
else
{
return null;
}
}
}
}