1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-24 12:35:25 +01:00

[PM-10394] Add new item type ssh key (#4575)

* Add ssh key item type

* Add fingerprint

* Limit ssh key ciphers to new clients

* Fix enc string length for 4096 bit rsa keys

* Remove keyAlgorithm from ssh cipher

* Add featureflag and exclude mobile from sync

* Add ssh-agent flag
This commit is contained in:
Bernd Schoolmann 2024-11-05 20:25:06 +01:00 committed by GitHub
parent 50f7fa03db
commit dae493db72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 87 additions and 1 deletions

View File

@ -1,7 +1,9 @@
using Bit.Api.Vault.Models.Response; using Bit.Api.Vault.Models.Response;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions; using Bit.Core.Exceptions;
@ -10,6 +12,7 @@ using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Settings; using Bit.Core.Settings;
using Bit.Core.Tools.Repositories; using Bit.Core.Tools.Repositories;
using Bit.Core.Vault.Models.Data;
using Bit.Core.Vault.Repositories; using Bit.Core.Vault.Repositories;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -30,6 +33,8 @@ public class SyncController : Controller
private readonly IPolicyRepository _policyRepository; private readonly IPolicyRepository _policyRepository;
private readonly ISendRepository _sendRepository; private readonly ISendRepository _sendRepository;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly ICurrentContext _currentContext;
private readonly Version _sshKeyCipherMinimumVersion = new(Constants.SSHKeyCipherMinimumVersion);
private readonly IFeatureService _featureService; private readonly IFeatureService _featureService;
public SyncController( public SyncController(
@ -43,6 +48,7 @@ public class SyncController : Controller
IPolicyRepository policyRepository, IPolicyRepository policyRepository,
ISendRepository sendRepository, ISendRepository sendRepository,
GlobalSettings globalSettings, GlobalSettings globalSettings,
ICurrentContext currentContext,
IFeatureService featureService) IFeatureService featureService)
{ {
_userService = userService; _userService = userService;
@ -55,6 +61,7 @@ public class SyncController : Controller
_policyRepository = policyRepository; _policyRepository = policyRepository;
_sendRepository = sendRepository; _sendRepository = sendRepository;
_globalSettings = globalSettings; _globalSettings = globalSettings;
_currentContext = currentContext;
_featureService = featureService; _featureService = featureService;
} }
@ -77,7 +84,8 @@ public class SyncController : Controller
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled); var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
var folders = await _folderRepository.GetManyByUserIdAsync(user.Id); var folders = await _folderRepository.GetManyByUserIdAsync(user.Id);
var ciphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: hasEnabledOrgs); var allCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id, withOrganizations: hasEnabledOrgs);
var ciphers = FilterSSHKeys(allCiphers);
var sends = await _sendRepository.GetManyByUserIdAsync(user.Id); var sends = await _sendRepository.GetManyByUserIdAsync(user.Id);
IEnumerable<CollectionDetails> collections = null; IEnumerable<CollectionDetails> collections = null;
@ -101,4 +109,16 @@ public class SyncController : Controller
folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends); folders, collections, ciphers, collectionCiphersGroupDict, excludeDomains, policies, sends);
return response; return response;
} }
private ICollection<CipherDetails> FilterSSHKeys(ICollection<CipherDetails> ciphers)
{
if (_currentContext.ClientVersion >= _sshKeyCipherMinimumVersion)
{
return ciphers;
}
else
{
return ciphers.Where(c => c.Type != Core.Vault.Enums.CipherType.SSHKey).ToList();
}
}
} }

View File

@ -0,0 +1,26 @@
using Bit.Core.Utilities;
using Bit.Core.Vault.Models.Data;
namespace Bit.Api.Vault.Models;
public class CipherSSHKeyModel
{
public CipherSSHKeyModel() { }
public CipherSSHKeyModel(CipherSSHKeyData data)
{
PrivateKey = data.PrivateKey;
PublicKey = data.PublicKey;
KeyFingerprint = data.KeyFingerprint;
}
[EncryptedString]
[EncryptedStringLength(5000)]
public string PrivateKey { get; set; }
[EncryptedString]
[EncryptedStringLength(5000)]
public string PublicKey { get; set; }
[EncryptedString]
[EncryptedStringLength(1000)]
public string KeyFingerprint { get; set; }
}

View File

@ -37,6 +37,7 @@ public class CipherRequestModel
public CipherCardModel Card { get; set; } public CipherCardModel Card { get; set; }
public CipherIdentityModel Identity { get; set; } public CipherIdentityModel Identity { get; set; }
public CipherSecureNoteModel SecureNote { get; set; } public CipherSecureNoteModel SecureNote { get; set; }
public CipherSSHKeyModel SSHKey { get; set; }
public DateTime? LastKnownRevisionDate { get; set; } = null; public DateTime? LastKnownRevisionDate { get; set; } = null;
public CipherDetails ToCipherDetails(Guid userId, bool allowOrgIdSet = true) public CipherDetails ToCipherDetails(Guid userId, bool allowOrgIdSet = true)
@ -82,6 +83,9 @@ public class CipherRequestModel
case CipherType.SecureNote: case CipherType.SecureNote:
existingCipher.Data = JsonSerializer.Serialize(ToCipherSecureNoteData(), JsonHelpers.IgnoreWritingNull); existingCipher.Data = JsonSerializer.Serialize(ToCipherSecureNoteData(), JsonHelpers.IgnoreWritingNull);
break; break;
case CipherType.SSHKey:
existingCipher.Data = JsonSerializer.Serialize(ToCipherSSHKeyData(), JsonHelpers.IgnoreWritingNull);
break;
default: default:
throw new ArgumentException("Unsupported type: " + nameof(Type) + "."); throw new ArgumentException("Unsupported type: " + nameof(Type) + ".");
} }
@ -230,6 +234,21 @@ public class CipherRequestModel
Type = SecureNote.Type, Type = SecureNote.Type,
}; };
} }
private CipherSSHKeyData ToCipherSSHKeyData()
{
return new CipherSSHKeyData
{
Name = Name,
Notes = Notes,
Fields = Fields?.Select(f => f.ToCipherFieldData()),
PasswordHistory = PasswordHistory?.Select(ph => ph.ToCipherPasswordHistoryData()),
PrivateKey = SSHKey.PrivateKey,
PublicKey = SSHKey.PublicKey,
KeyFingerprint = SSHKey.KeyFingerprint,
};
}
} }
public class CipherWithIdRequestModel : CipherRequestModel public class CipherWithIdRequestModel : CipherRequestModel

View File

@ -48,6 +48,12 @@ public class CipherMiniResponseModel : ResponseModel
cipherData = identityData; cipherData = identityData;
Identity = new CipherIdentityModel(identityData); Identity = new CipherIdentityModel(identityData);
break; break;
case CipherType.SSHKey:
var sshKeyData = JsonSerializer.Deserialize<CipherSSHKeyData>(cipher.Data);
Data = sshKeyData;
cipherData = sshKeyData;
SSHKey = new CipherSSHKeyModel(sshKeyData);
break;
default: default:
throw new ArgumentException("Unsupported " + nameof(Type) + "."); throw new ArgumentException("Unsupported " + nameof(Type) + ".");
} }
@ -76,6 +82,7 @@ public class CipherMiniResponseModel : ResponseModel
public CipherCardModel Card { get; set; } public CipherCardModel Card { get; set; }
public CipherIdentityModel Identity { get; set; } public CipherIdentityModel Identity { get; set; }
public CipherSecureNoteModel SecureNote { get; set; } public CipherSecureNoteModel SecureNote { get; set; }
public CipherSSHKeyModel SSHKey { get; set; }
public IEnumerable<CipherFieldModel> Fields { get; set; } public IEnumerable<CipherFieldModel> Fields { get; set; }
public IEnumerable<CipherPasswordHistoryModel> PasswordHistory { get; set; } public IEnumerable<CipherPasswordHistoryModel> PasswordHistory { get; set; }
public IEnumerable<AttachmentResponseModel> Attachments { get; set; } public IEnumerable<AttachmentResponseModel> Attachments { get; set; }

View File

@ -22,6 +22,7 @@ public static class Constants
public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60; public const int OrganizationSelfHostSubscriptionGracePeriodDays = 60;
public const string Fido2KeyCipherMinimumVersion = "2023.10.0"; public const string Fido2KeyCipherMinimumVersion = "2023.10.0";
public const string SSHKeyCipherMinimumVersion = "2024.12.0";
/// <summary> /// <summary>
/// Used by IdentityServer to identify our own provider. /// Used by IdentityServer to identify our own provider.
@ -122,6 +123,8 @@ public static class FeatureFlagKeys
public const string InlineMenuPositioningImprovements = "inline-menu-positioning-improvements"; public const string InlineMenuPositioningImprovements = "inline-menu-positioning-improvements";
public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner"; public const string ProviderClientVaultPrivacyBanner = "ac-2833-provider-client-vault-privacy-banner";
public const string DeviceTrustLogging = "pm-8285-device-trust-logging"; public const string DeviceTrustLogging = "pm-8285-device-trust-logging";
public const string SSHKeyItemVaultItem = "ssh-key-vault-item";
public const string SSHAgent = "ssh-agent";
public const string AuthenticatorTwoFactorToken = "authenticator-2fa-token"; public const string AuthenticatorTwoFactorToken = "authenticator-2fa-token";
public const string EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub"; public const string EnableUpgradePasswordManagerSub = "AC-2708-upgrade-password-manager-sub";
public const string IdpAutoSubmitLogin = "idp-auto-submit-login"; public const string IdpAutoSubmitLogin = "idp-auto-submit-login";

View File

@ -8,4 +8,5 @@ public enum CipherType : byte
SecureNote = 2, SecureNote = 2,
Card = 3, Card = 3,
Identity = 4, Identity = 4,
SSHKey = 5,
} }

View File

@ -0,0 +1,10 @@
namespace Bit.Core.Vault.Models.Data;
public class CipherSSHKeyData : CipherData
{
public CipherSSHKeyData() { }
public string PrivateKey { get; set; }
public string PublicKey { get; set; }
public string KeyFingerprint { get; set; }
}