diff --git a/src/Api/Tools/Models/Request/SendRequestModel.cs b/src/Api/Tools/Models/Request/SendRequestModel.cs
index 00dcb6273f..f3308dbd5a 100644
--- a/src/Api/Tools/Models/Request/SendRequestModel.cs
+++ b/src/Api/Tools/Models/Request/SendRequestModel.cs
@@ -102,16 +102,8 @@ public class SendRequestModel
/// Comma-separated list of emails that may access the send using OTP
/// authentication. Mutually exclusive with .
///
- [EncryptedString]
- [EncryptedStringLength(4000)]
- public string Emails { get; set; }
-
- ///
- /// Comma-separated list of email **hashes** that may access the send using OTP
- /// authentication. Mutually exclusive with .
- ///
[StringLength(4000)]
- public string EmailHashes { get; set; }
+ public string Emails { get; set; }
///
/// When , send access is disabled.
@@ -261,7 +253,6 @@ public class SendRequestModel
// normalize encoding
var emails = Emails.Split(',', RemoveEmptyEntries | TrimEntries);
existingSend.Emails = string.Join(",", emails);
- existingSend.EmailHashes = EmailHashes;
existingSend.Password = null;
existingSend.AuthType = Core.Tools.Enums.AuthType.Email;
}
diff --git a/src/Core/Tools/Entities/Send.cs b/src/Core/Tools/Entities/Send.cs
index c4398e212c..52b439c41e 100644
--- a/src/Core/Tools/Entities/Send.cs
+++ b/src/Core/Tools/Entities/Send.cs
@@ -81,15 +81,6 @@ public class Send : ITableObject
[MaxLength(4000)]
public string? Emails { get; set; }
- ///
- /// Comma-separated list of email **hashes** for OTP authentication.
- ///
- ///
- /// This field is mutually exclusive with
- ///
- [MaxLength(4000)]
- public string? EmailHashes { get; set; }
-
///
/// The send becomes unavailable to API callers when
/// >= .
diff --git a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
index 769e9df713..21d0822c90 100644
--- a/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
+++ b/src/Core/Tools/Models/Data/SendAuthenticationTypes.cs
@@ -44,7 +44,7 @@ public record ResourcePassword(string Hash) : SendAuthenticationMethod;
///
/// Create a send claim by requesting a one time password (OTP) confirmation code.
///
-///
-/// The list of email address **hashes** permitted access to the send.
+///
+/// The list of email addresses permitted access to the send.
///
-public record EmailOtp(string[] EmailHashes) : SendAuthenticationMethod;
+public record EmailOtp(string[] emails) : SendAuthenticationMethod;
diff --git a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
index a82c27d0c3..6c7b965ef2 100644
--- a/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
+++ b/src/Core/Tools/SendFeatures/Queries/SendAuthenticationQuery.cs
@@ -41,7 +41,7 @@ public class SendAuthenticationQuery : ISendAuthenticationQuery
var s when s.AccessCount >= s.MaxAccessCount.GetValueOrDefault(int.MaxValue) => NEVER_AUTHENTICATE,
var s when s.ExpirationDate.GetValueOrDefault(DateTime.MaxValue) < DateTime.UtcNow => NEVER_AUTHENTICATE,
var s when s.DeletionDate <= DateTime.UtcNow => NEVER_AUTHENTICATE,
- var s when s.AuthType == AuthType.Email && s.EmailHashes is not null => EmailOtp(s.EmailHashes),
+ var s when s.AuthType == AuthType.Email && s.Emails is not null => EmailOtp(s.Emails),
var s when s.AuthType == AuthType.Password && s.Password is not null => new ResourcePassword(s.Password),
_ => NOT_AUTHENTICATED
};
@@ -49,13 +49,13 @@ public class SendAuthenticationQuery : ISendAuthenticationQuery
return method;
}
- private static EmailOtp EmailOtp(string? emailHashes)
+ private static EmailOtp EmailOtp(string? emails)
{
- if (string.IsNullOrWhiteSpace(emailHashes))
+ if (string.IsNullOrWhiteSpace(emails))
{
return new EmailOtp([]);
}
- var list = emailHashes.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
+ var list = emails.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
return new EmailOtp(list);
}
}
diff --git a/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs b/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
index 8bbeeba2c0..02442d8c7e 100644
--- a/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
+++ b/src/Identity/IdentityServer/RequestValidators/SendAccess/SendEmailOtpRequestValidator.cs
@@ -1,6 +1,4 @@
using System.Security.Claims;
-using System.Security.Cryptography;
-using System.Text;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Services;
@@ -39,17 +37,14 @@ public class SendEmailOtpRequestValidator(
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailRequired);
}
- // email hash must be in the list of email hashes in the EmailOtp array
- byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(email));
- string hashEmailHex = Convert.ToHexString(hashBytes).ToUpperInvariant();
/*
* This is somewhat contradictory to our process where a poor shape means invalid_request and invalid
* data is invalid_grant.
* In this case the shape is correct and the data is invalid but to protect against enumeration we treat incorrect emails
* as invalid requests. The response for a request with a correct email which needs an OTP and a request
* that has an invalid email need to be the same otherwise an attacker could enumerate until a valid email is found.
- */
- if (!authMethod.EmailHashes.Contains(hashEmailHex))
+ */
+ if (!authMethod.emails.Contains(email, StringComparer.OrdinalIgnoreCase))
{
return BuildErrorResult(SendAccessConstants.EmailOtpValidatorResults.EmailAndOtpRequired);
}
diff --git a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
index 4c5d70340f..144e08021d 100644
--- a/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
+++ b/src/Infrastructure.Dapper/Tools/Repositories/SendRepository.cs
@@ -154,7 +154,7 @@ public class SendRepository : Repository, ISendRepository
}
// Capture original value
- var originalEmailHashes = send.EmailHashes;
+ var emails = send.Emails;
// Protect value
ProtectData(send);
@@ -163,15 +163,15 @@ public class SendRepository : Repository, ISendRepository
await saveTask();
// Restore original value
- send.EmailHashes = originalEmailHashes;
+ send.Emails = emails;
}
private void ProtectData(Send send)
{
- if (!send.EmailHashes?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
+ if (!send.Emails?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
{
- send.EmailHashes = string.Concat(Constants.DatabaseFieldProtectedPrefix,
- _dataProtector.Protect(send.EmailHashes!));
+ send.Emails = string.Concat(Constants.DatabaseFieldProtectedPrefix,
+ _dataProtector.Protect(send.Emails!));
}
}
@@ -182,10 +182,10 @@ public class SendRepository : Repository, ISendRepository
return;
}
- if (send.EmailHashes?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
+ if (send.Emails?.StartsWith(Constants.DatabaseFieldProtectedPrefix) ?? false)
{
- send.EmailHashes = _dataProtector.Unprotect(
- send.EmailHashes.Substring(Constants.DatabaseFieldProtectedPrefix.Length));
+ send.Emails = _dataProtector.Unprotect(
+ send.Emails.Substring(Constants.DatabaseFieldProtectedPrefix.Length));
}
}
diff --git a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
index 8821f39097..025fae802b 100644
--- a/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
+++ b/src/Infrastructure.EntityFramework/Repositories/DatabaseContext.cs
@@ -150,7 +150,7 @@ public class DatabaseContext : DbContext
var dataProtectionConverter = new DataProtectionConverter(dataProtector);
eUser.Property(c => c.Key).HasConversion(dataProtectionConverter);
eUser.Property(c => c.MasterPassword).HasConversion(dataProtectionConverter);
- eSend.Property(c => c.EmailHashes).HasConversion(dataProtectionConverter);
+ eSend.Property(c => c.Emails).HasConversion(dataProtectionConverter);
if (Database.IsNpgsql())
{
diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
index e277174717..752f8fb496 100644
--- a/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
+++ b/src/Sql/dbo/Tools/Stored Procedures/Send_Create.sql
@@ -18,8 +18,7 @@
-- FIXME: remove null default value once this argument has been
-- in 2 server releases
@Emails NVARCHAR(4000) = NULL,
- @AuthType TINYINT = NULL,
- @EmailHashes NVARCHAR(4000) = NULL
+ @AuthType TINYINT = NULL
AS
BEGIN
SET NOCOUNT ON
@@ -43,8 +42,7 @@ BEGIN
[HideEmail],
[CipherId],
[Emails],
- [AuthType],
- [EmailHashes]
+ [AuthType]
)
VALUES
(
@@ -65,8 +63,7 @@ BEGIN
@HideEmail,
@CipherId,
@Emails,
- @AuthType,
- @EmailHashes
+ @AuthType
)
IF @UserId IS NOT NULL
diff --git a/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql b/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
index a2bcb0a24b..fba842d8d6 100644
--- a/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
+++ b/src/Sql/dbo/Tools/Stored Procedures/Send_Update.sql
@@ -16,8 +16,7 @@
@HideEmail BIT,
@CipherId UNIQUEIDENTIFIER = NULL,
@Emails NVARCHAR(4000) = NULL,
- @AuthType TINYINT = NULL,
- @EmailHashes NVARCHAR(4000) = NULL
+ @AuthType TINYINT = NULL
AS
BEGIN
SET NOCOUNT ON
@@ -41,8 +40,7 @@ BEGIN
[HideEmail] = @HideEmail,
[CipherId] = @CipherId,
[Emails] = @Emails,
- [AuthType] = @AuthType,
- [EmailHashes] = @EmailHashes
+ [AuthType] = @AuthType
WHERE
[Id] = @Id
diff --git a/src/Sql/dbo/Tools/Tables/Send.sql b/src/Sql/dbo/Tools/Tables/Send.sql
index 59a42a2aa5..d7cea28383 100644
--- a/src/Sql/dbo/Tools/Tables/Send.sql
+++ b/src/Sql/dbo/Tools/Tables/Send.sql
@@ -18,7 +18,6 @@
[HideEmail] BIT NULL,
[CipherId] UNIQUEIDENTIFIER NULL,
[AuthType] TINYINT NULL,
- [EmailHashes] NVARCHAR(4000) NULL,
CONSTRAINT [PK_Send] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_Send_Organization] FOREIGN KEY ([OrganizationId]) REFERENCES [dbo].[Organization] ([Id]),
CONSTRAINT [FK_Send_User] FOREIGN KEY ([UserId]) REFERENCES [dbo].[User] ([Id]),
diff --git a/test/Common/Helpers/CryptographyHelper.cs b/test/Common/Helpers/CryptographyHelper.cs
deleted file mode 100644
index 30dfb1a679..0000000000
--- a/test/Common/Helpers/CryptographyHelper.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System.Security.Cryptography;
-using System.Text;
-
-namespace Bit.Test.Common.Helpers;
-
-public class CryptographyHelper
-{
- ///
- /// Returns a hex-encoded, SHA256 hash for the given string
- ///
- public static string HashAndEncode(string text)
- {
- var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(text));
- var hashEncoded = Convert.ToHexString(hashBytes).ToUpperInvariant();
- return hashEncoded;
- }
-}
diff --git a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
index b4b1ecbc79..87880998c3 100644
--- a/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
+++ b/test/Core.Test/Tools/Services/SendAuthenticationQueryTests.cs
@@ -43,12 +43,12 @@ public class SendAuthenticationQueryTests
}
[Theory]
- [MemberData(nameof(EmailHashesParsingTestCases))]
- public async Task GetAuthenticationMethod_WithEmailHashes_ParsesEmailHashesCorrectly(string emailHashString, string[] expectedEmailHashes)
+ [MemberData(nameof(EmailsParsingTestCases))]
+ public async Task GetAuthenticationMethod_WithEmails_ParsesEmailsCorrectly(string emailString, string[] expectedEmails)
{
// Arrange
var sendId = Guid.NewGuid();
- var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: emailHashString, password: null, AuthType.Email);
+ var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: emailString, password: null, AuthType.Email);
_sendRepository.GetByIdAsync(sendId).Returns(send);
// Act
@@ -56,15 +56,15 @@ public class SendAuthenticationQueryTests
// Assert
var emailOtp = Assert.IsType(result);
- Assert.Equal(expectedEmailHashes, emailOtp.EmailHashes);
+ Assert.Equal(expectedEmails, emailOtp.emails);
}
[Fact]
- public async Task GetAuthenticationMethod_WithBothEmailHashesAndPassword_ReturnsEmailOtp()
+ public async Task GetAuthenticationMethod_WithBothEmailsAndPassword_ReturnsEmailOtp()
{
// Arrange
var sendId = Guid.NewGuid();
- var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: "hashedemail", password: "hashedpassword", AuthType.Email);
+ var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: "person@company.com", password: "hashedpassword", AuthType.Email);
_sendRepository.GetByIdAsync(sendId).Returns(send);
// Act
@@ -79,7 +79,7 @@ public class SendAuthenticationQueryTests
{
// Arrange
var sendId = Guid.NewGuid();
- var send = CreateSend(accessCount: 0, maxAccessCount: 10, emailHashes: null, password: null, AuthType.None);
+ var send = CreateSend(accessCount: 0, maxAccessCount: 10, emails: null, password: null, AuthType.None);
_sendRepository.GetByIdAsync(sendId).Returns(send);
// Act
@@ -106,11 +106,11 @@ public class SendAuthenticationQueryTests
public static IEnumerable