Skip to content

Commit

Permalink
[PM-1222] Passkeys in the Bitwarden vault (#2679)
Browse files Browse the repository at this point in the history
* [EC-598] feat: add support for saving fido2 keys

* [EC-598] feat: add additional data

* [EC-598] feat: add counter, nonDiscoverableId; remove origin

* [EC-598] fix: previous incomplete commit

* [EC-598] fix: previous incomplete commit.. again

* [EC-598] fix: failed merge

* [EC-598] fix: move files around to match new structure

* [EC-598] feat: add implementation for non-discoverable credentials

* [EC-598] chore: remove some changes introduced by vs

* [EC-598] fix: linting issues

* [PM-1500] Add feature flag to enable pass keys (#2916)

* Added feature flag to enable pass keys

* Renamed enable pass keys to fido2 vault credentials

* only sync fido2key ciphers on clients >=2023.9.0 (#3244)

* Renamed fido2key property username to userDisplayName (#3172)

* [PM-1859] Renamed NonDiscoverableId to credentialId (#3198)

* PM-1859 Refactor to credentialId

* PM-1859 Removed unnecessary import

---------

Co-authored-by: Andreas Coroiu <andreas.coroiu@gmail.com>

* [PM-3807] Store all passkeys as login cipher type (#3261)

* [PM-3807] feat: add discoverable property to fido2key

* [PM-3807] feat: remove standalone Fido2Key

* [PM-3807] chore: clean up unusued constant

* [PM-3807] fix: remove standadlone Fido2Key property that I missed

* [PM-3807] Store passkeys in array (#3268)

* [PM-3807] feat: store passkeys in array

* [PM-3807] amazing adventures with the c# linter

* [PM-3980] Added creationDate property to the Fido2Key object (#3279)

* Added creationDate property to the Fido2Key object

* Fixed lint issues

* fixed comments

* made createionDate required

* [PM-3808] [Storage v2] Add old client/new server backward compatibility (#3262)

* [PM-3807] feat: add discoverable property to fido2key

* [PM-3807] feat: remove standalone Fido2Key

* [PM-3807] chore: clean up unusued constant

* [PM-3808] feat: add fido2 compatibility check before saving ciphers

* Resolved merge conflicts.

* Setting minimum version for QA.

---------

Co-authored-by: Todd Martin <tmartin@bitwarden.com>

* [PM-4054] Rename Fido2Key to Fido2Credential (#3306)

* Add server version compatibility check for Fido2Credentials on sharing with org (#3328)

* Added compatibility checks.

* Refactored into separate methods for easier removal.

* Added check on ShareMany

* Updated method order to be consistent.

* Linting

* Updated minimum server version for release, as well as defaulting the feature on for self-hosted.

* Added trailing space.

* Removed extra assignment

---------

Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
Co-authored-by: SmithThe4th <gsmith@bitwarden.com>
Co-authored-by: Todd Martin <tmartin@bitwarden.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
Co-authored-by: Carlos Gonçalves <carlosmaccam@gmail.com>
Co-authored-by: Todd Martin <106564991+trmartin4@users.noreply.github.com>
Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
  • Loading branch information
8 people committed Oct 17, 2023
1 parent 8177821 commit 8c77c65
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 7 deletions.
34 changes: 29 additions & 5 deletions src/Api/Vault/Controllers/CiphersController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ namespace Bit.Api.Vault.Controllers;
[Authorize("Application")]
public class CiphersController : Controller
{
private static readonly Version _fido2KeyCipherMinimumVersion = new Version(Constants.Fido2KeyCipherMinimumVersion);

private readonly ICipherRepository _cipherRepository;
private readonly ICollectionCipherRepository _collectionCipherRepository;
private readonly ICipherService _cipherService;
Expand Down Expand Up @@ -178,7 +180,8 @@ public async Task<CipherResponseModel> Put(Guid id, [FromBody] CipherRequestMode
throw new NotFoundException();
}

ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);

var collectionIds = (await _collectionCipherRepository.GetManyByUserIdCipherIdAsync(userId, id)).Select(c => c.CollectionId).ToList();
var modelOrgId = string.IsNullOrWhiteSpace(model.OrganizationId) ?
Expand All @@ -202,7 +205,8 @@ public async Task<CipherMiniResponseModel> PutAdmin(Guid id, [FromBody] CipherRe
var userId = _userService.GetProperUserId(User).Value;
var cipher = await _cipherRepository.GetOrganizationDetailsByIdAsync(id);

ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);

if (cipher == null || !cipher.OrganizationId.HasValue ||
!await _currentContext.EditAnyCollection(cipher.OrganizationId.Value))
Expand Down Expand Up @@ -267,6 +271,9 @@ public async Task<CipherResponseModel> PutShare(string id, [FromBody] CipherShar
throw new NotFoundException();
}

ValidateClientVersionForItemLevelEncryptionSupport(cipher);
ValidateClientVersionForFido2CredentialSupport(cipher);

var original = cipher.Clone();
await _cipherService.ShareAsync(original, model.Cipher.ToCipher(cipher), new Guid(model.Cipher.OrganizationId),
model.CollectionIds.Select(c => new Guid(c)), userId, model.Cipher.LastKnownRevisionDate);
Expand Down Expand Up @@ -529,7 +536,12 @@ public async Task PutShareMany([FromBody] CipherBulkShareRequestModel model)
throw new BadRequestException("Trying to move ciphers that you do not own.");
}

shareCiphers.Add((cipher.ToCipher(ciphersDict[cipher.Id.Value]), cipher.LastKnownRevisionDate));
var existingCipher = ciphersDict[cipher.Id.Value];

ValidateClientVersionForItemLevelEncryptionSupport(existingCipher);
ValidateClientVersionForFido2CredentialSupport(existingCipher);

shareCiphers.Add((cipher.ToCipher(existingCipher), cipher.LastKnownRevisionDate));
}

await _cipherService.ShareManyAsync(shareCiphers, organizationId,
Expand Down Expand Up @@ -582,7 +594,7 @@ await _cipherRepository.GetOrganizationDetailsByIdAsync(idGuid) :
throw new NotFoundException();
}

ValidateItemLevelEncryptionIsAvailable(cipher);
ValidateClientVersionForItemLevelEncryptionSupport(cipher);

if (request.FileSize > CipherService.MAX_FILE_SIZE)
{
Expand Down Expand Up @@ -804,11 +816,23 @@ private void ValidateAttachment()
}
}

private void ValidateItemLevelEncryptionIsAvailable(Cipher cipher)
private void ValidateClientVersionForItemLevelEncryptionSupport(Cipher cipher)
{
if (cipher.Key != null && _currentContext.ClientVersion < _cipherKeyEncryptionMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}

private void ValidateClientVersionForFido2CredentialSupport(Cipher cipher)
{
if (cipher.Type == Core.Vault.Enums.CipherType.Login)
{
var loginData = JsonSerializer.Deserialize<CipherLoginData>(cipher.Data);
if (loginData?.Fido2Credentials != null && _currentContext.ClientVersion < _fido2KeyCipherMinimumVersion)
{
throw new BadRequestException("Cannot edit item. Update to the latest version of Bitwarden and try again.");
}
}
}
}
89 changes: 89 additions & 0 deletions src/Api/Vault/Models/CipherFido2CredentialModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.ComponentModel.DataAnnotations;
using Bit.Core.Utilities;
using Bit.Core.Vault.Models.Data;

namespace Bit.Api.Vault.Models;

public class CipherFido2CredentialModel
{
public CipherFido2CredentialModel() { }

public CipherFido2CredentialModel(CipherLoginFido2CredentialData data)
{
CredentialId = data.CredentialId;
KeyType = data.KeyType;
KeyAlgorithm = data.KeyAlgorithm;
KeyCurve = data.KeyCurve;
KeyValue = data.KeyValue;
RpId = data.RpId;
RpName = data.RpName;
UserHandle = data.UserHandle;
UserDisplayName = data.UserDisplayName;
Counter = data.Counter;
Discoverable = data.Discoverable;
CreationDate = data.CreationDate;
}

[EncryptedString]
[EncryptedStringLength(1000)]
public string CredentialId { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyType { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyAlgorithm { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyCurve { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyValue { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string RpId { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string RpName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string UserHandle { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string UserDisplayName { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Counter { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string Discoverable { get; set; }
[Required]
public DateTime CreationDate { get; set; }

public CipherLoginFido2CredentialData ToCipherLoginFido2CredentialData()
{
return new CipherLoginFido2CredentialData
{
CredentialId = CredentialId,
KeyType = KeyType,
KeyAlgorithm = KeyAlgorithm,
KeyCurve = KeyCurve,
KeyValue = KeyValue,
RpId = RpId,
RpName = RpName,
UserHandle = UserHandle,
UserDisplayName = UserDisplayName,
Counter = Counter,
Discoverable = Discoverable,
CreationDate = CreationDate
};
}
}

static class CipherFido2CredentialModelExtensions
{
public static CipherLoginFido2CredentialData[] ToCipherLoginFido2CredentialData(this CipherFido2CredentialModel[] models)
{
return models.Select(m => m.ToCipherLoginFido2CredentialData()).ToArray();
}
}
6 changes: 6 additions & 0 deletions src/Api/Vault/Models/CipherLoginModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ public CipherLoginModel(CipherLoginData data)
Uri = data.Uri;
}

if (data.Fido2Credentials != null)
{
Fido2Credentials = data.Fido2Credentials.Select(key => new CipherFido2CredentialModel(key)).ToArray();
}

Username = data.Username;
Password = data.Password;
PasswordRevisionDate = data.PasswordRevisionDate;
Expand Down Expand Up @@ -55,6 +60,7 @@ public string Uri
[EncryptedStringLength(1000)]
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public CipherFido2CredentialModel[] Fido2Credentials { get; set; }

public class CipherLoginUriModel
{
Expand Down
1 change: 1 addition & 0 deletions src/Api/Vault/Models/Request/CipherRequestModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ private CipherLoginData ToCipherLoginData()
PasswordRevisionDate = Login.PasswordRevisionDate,
Totp = Login.Totp,
AutofillOnPageLoad = Login.AutofillOnPageLoad,
Fido2Credentials = Login.Fido2Credentials == null ? null : Login.Fido2Credentials.ToCipherLoginFido2CredentialData(),
};
}

Expand Down
6 changes: 5 additions & 1 deletion src/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public static class Constants
/// </summary>
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;

public const string Fido2KeyCipherMinimumVersion = "2023.10.0";

public const string CipherKeyEncryptionMinimumVersion = "2023.9.2";
}

Expand All @@ -38,6 +40,7 @@ public static class FeatureFlagKeys
public const string DisplayEuEnvironment = "display-eu-environment";
public const string DisplayLowKdfIterationWarning = "display-kdf-iteration-warning";
public const string TrustedDeviceEncryption = "trusted-device-encryption";
public const string Fido2VaultCredentials = "fido2-vault-credentials";
public const string AutofillV2 = "autofill-v2";
public const string BrowserFilelessImport = "browser-fileless-import";

Expand All @@ -54,7 +57,8 @@ public static Dictionary<string, string> GetLocalOverrideFlagValues()
// place overriding values when needed locally (offline), or return null
return new Dictionary<string, string>()
{
{ TrustedDeviceEncryption, "true" }
{ TrustedDeviceEncryption, "true" },
{ Fido2VaultCredentials, "true" }
};
}
}
2 changes: 1 addition & 1 deletion src/Core/Vault/Enums/CipherType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ public enum CipherType : byte
Login = 1,
SecureNote = 2,
Card = 3,
Identity = 4
Identity = 4,
}
1 change: 1 addition & 0 deletions src/Core/Vault/Models/Data/CipherLoginData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public string Uri
public DateTime? PasswordRevisionDate { get; set; }
public string Totp { get; set; }
public bool? AutofillOnPageLoad { get; set; }
public CipherLoginFido2CredentialData[] Fido2Credentials { get; set; }

public class CipherLoginUriData
{
Expand Down
19 changes: 19 additions & 0 deletions src/Core/Vault/Models/Data/CipherLoginFido2CredentialData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace Bit.Core.Vault.Models.Data;

public class CipherLoginFido2CredentialData
{
public CipherLoginFido2CredentialData() { }

public string CredentialId { get; set; }
public string KeyType { get; set; }
public string KeyAlgorithm { get; set; }
public string KeyCurve { get; set; }
public string KeyValue { get; set; }
public string RpId { get; set; }
public string RpName { get; set; }
public string UserHandle { get; set; }
public string UserDisplayName { get; set; }
public string Counter { get; set; }
public string Discoverable { get; set; }
public DateTime CreationDate { get; set; }
}

0 comments on commit 8c77c65

Please sign in to comment.