mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
PM-10393 SSH keys (#10825)
* [PM-10395] Add new item type ssh key (#10360) * Implement ssh-key cipher type * Fix linting * Fix edit and view components for ssh-keys on desktop * Fix tests * Remove ssh key type references * Remove add ssh key option * Fix typo * Add tests * [PM-10399] Add ssh key import export for bitwarden json (#10529) * Add ssh key import export for bitwarden json * Remove key type from ssh key export * [PM-10406] Add privatekey publickey and fingerprint to both add-edit and view co… (#11046) * Add privatekey publickey and fingerprint to both add-edit and view components * Remove wrong a11y title * Fix testid * [PM-10098] SSH Agent & SSH Key creation for Bitwarden Desktop (#10293) * Add ssh agent, generator & import * Move ssh agent code to bitwarden-russh crate * Remove generator component * Cleanup * Cleanup * Remove left over sshGenerator reference * Cleanup * Add documentation to sshkeyimportstatus * Fix outdated variable name * Update apps/desktop/src/platform/preload.ts Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Rename renderersshagent * Rename MainSshAgentService * Improve clarity of 'id' variables being used * Improve clarity of 'id' variables being used * Update apps/desktop/src/vault/app/vault/add-edit.component.html Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Fix outdated cipher/messageid names * Rename SSH to Ssh * Make agent syncing more reactive * Move constants to top of class * Make sshkey cipher filtering clearer * Add stricter equality check on ssh key unlock * Fix build and messages * Fix incorrect featureflag name * Replace anonymous async function with switchmap pipe * Fix build * Update apps/desktop/desktop_native/napi/src/lib.rs Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Revert incorrectly renamed 'Ssh' usages to SSH * Run cargo fmt * Clean up ssh agent sock path logic * Cleanup and split to platform specific files * Small cleanup * Pull out generator and importer into core * Rename renderersshagentservice to sshagentservice * Rename cipheruuid to cipher_id * Drop ssh dependencies from napi crate * Clean up windows build * Small cleanup * Small cleanup * Cleanup * Add rxjs pipeline for agent services * [PM-12555] Pkcs8 sshkey import & general ssh key import tests (#11048) * Add pkcs8 import and tests * Add key type unsupported error * Remove unsupported formats * Remove code for unsupported formats * Fix encrypted pkcs8 import * Add ed25519 pkcs8 unencrypted test file * SSH agent rxjs tweaks (#11148) * feat: rewrite sshagent.signrequest as purely observable * feat: fail the request when unlock times out * chore: clean up, add some clarifying comments * chore: remove unused dependency * fix: result `undefined` crashing in NAPI -> Rust * Allow concurrent SSH requests in rust * Remove unwraps * Cleanup and add init service init call * Fix windows * Fix timeout behavior on locked vault --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> * Fix libc dependency being duplicated * fix SSH casing (#11840) * Move ssh agent behind feature flag (#11841) * Move ssh agent behind feature flag * Add separate flag for ssh agent * [PM-14215] fix unsupported key type error message (#11788) * Fix error message for import of unsupported ssh keys * Use triple equals in add-edit component for ssh keys --------- Co-authored-by: Andreas Coroiu <acoroiu@bitwarden.com> Co-authored-by: aj-bw <81774843+aj-bw@users.noreply.github.com>
This commit is contained in:
@@ -27,6 +27,8 @@ export enum FeatureFlag {
|
||||
EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill",
|
||||
DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2",
|
||||
AccountDeprovisioning = "pm-10308-account-deprovisioning",
|
||||
SSHKeyVaultItem = "ssh-key-vault-item",
|
||||
SSHAgent = "ssh-agent",
|
||||
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
|
||||
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
|
||||
CipherKeyEncryption = "cipher-key-encryption",
|
||||
@@ -73,6 +75,8 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE,
|
||||
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
|
||||
[FeatureFlag.AccountDeprovisioning]: FALSE,
|
||||
[FeatureFlag.SSHKeyVaultItem]: FALSE,
|
||||
[FeatureFlag.SSHAgent]: FALSE,
|
||||
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
|
||||
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
|
||||
[FeatureFlag.CipherKeyEncryption]: FALSE,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { IdentityExport } from "./identity.export";
|
||||
import { LoginExport } from "./login.export";
|
||||
import { PasswordHistoryExport } from "./password-history.export";
|
||||
import { SecureNoteExport } from "./secure-note.export";
|
||||
import { SshKeyExport } from "./ssh-key.export";
|
||||
import { safeGetString } from "./utils";
|
||||
|
||||
export class CipherExport {
|
||||
@@ -27,6 +28,7 @@ export class CipherExport {
|
||||
req.secureNote = null;
|
||||
req.card = null;
|
||||
req.identity = null;
|
||||
req.sshKey = null;
|
||||
req.reprompt = CipherRepromptType.None;
|
||||
req.passwordHistory = [];
|
||||
req.creationDate = null;
|
||||
@@ -67,6 +69,8 @@ export class CipherExport {
|
||||
case CipherType.Identity:
|
||||
view.identity = IdentityExport.toView(req.identity);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
view.sshKey = SshKeyExport.toView(req.sshKey);
|
||||
}
|
||||
|
||||
if (req.passwordHistory != null) {
|
||||
@@ -108,6 +112,9 @@ export class CipherExport {
|
||||
case CipherType.Identity:
|
||||
domain.identity = IdentityExport.toDomain(req.identity);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
domain.sshKey = SshKeyExport.toDomain(req.sshKey);
|
||||
break;
|
||||
}
|
||||
|
||||
if (req.passwordHistory != null) {
|
||||
@@ -132,6 +139,7 @@ export class CipherExport {
|
||||
secureNote: SecureNoteExport;
|
||||
card: CardExport;
|
||||
identity: IdentityExport;
|
||||
sshKey: SshKeyExport;
|
||||
reprompt: CipherRepromptType;
|
||||
passwordHistory: PasswordHistoryExport[] = null;
|
||||
revisionDate: Date = null;
|
||||
@@ -171,6 +179,9 @@ export class CipherExport {
|
||||
case CipherType.Identity:
|
||||
this.identity = new IdentityExport(o.identity);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
this.sshKey = new SshKeyExport(o.sshKey);
|
||||
break;
|
||||
}
|
||||
|
||||
if (o.passwordHistory != null) {
|
||||
|
||||
44
libs/common/src/models/export/ssh-key.export.ts
Normal file
44
libs/common/src/models/export/ssh-key.export.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
|
||||
|
||||
import { EncString } from "../../platform/models/domain/enc-string";
|
||||
import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key";
|
||||
|
||||
import { safeGetString } from "./utils";
|
||||
|
||||
export class SshKeyExport {
|
||||
static template(): SshKeyExport {
|
||||
const req = new SshKeyExport();
|
||||
req.privateKey = "";
|
||||
req.publicKey = "";
|
||||
req.keyFingerprint = "";
|
||||
return req;
|
||||
}
|
||||
|
||||
static toView(req: SshKeyExport, view = new SshKeyView()) {
|
||||
view.privateKey = req.privateKey;
|
||||
view.publicKey = req.publicKey;
|
||||
view.keyFingerprint = req.keyFingerprint;
|
||||
return view;
|
||||
}
|
||||
|
||||
static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
|
||||
domain.privateKey = req.privateKey != null ? new EncString(req.privateKey) : null;
|
||||
domain.publicKey = req.publicKey != null ? new EncString(req.publicKey) : null;
|
||||
domain.keyFingerprint = req.keyFingerprint != null ? new EncString(req.keyFingerprint) : null;
|
||||
return domain;
|
||||
}
|
||||
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
keyFingerprint: string;
|
||||
|
||||
constructor(o?: SshKeyView | SshKeyDomain) {
|
||||
if (o == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.privateKey = safeGetString(o.privateKey);
|
||||
this.publicKey = safeGetString(o.publicKey);
|
||||
this.keyFingerprint = safeGetString(o.keyFingerprint);
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ export enum CipherType {
|
||||
SecureNote = 2,
|
||||
Card = 3,
|
||||
Identity = 4,
|
||||
SshKey = 5,
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show
|
||||
case CipherType.Identity:
|
||||
icon = "bwi-id-card";
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
icon = "bwi-key";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
17
libs/common/src/vault/models/api/ssh-key.api.ts
Normal file
17
libs/common/src/vault/models/api/ssh-key.api.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { BaseResponse } from "../../../models/response/base.response";
|
||||
|
||||
export class SshKeyApi extends BaseResponse {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
keyFingerprint: string;
|
||||
|
||||
constructor(data: any = null) {
|
||||
super(data);
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
this.privateKey = this.getResponseProperty("PrivateKey");
|
||||
this.publicKey = this.getResponseProperty("PublicKey");
|
||||
this.keyFingerprint = this.getResponseProperty("KeyFingerprint");
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import { IdentityData } from "./identity.data";
|
||||
import { LoginData } from "./login.data";
|
||||
import { PasswordHistoryData } from "./password-history.data";
|
||||
import { SecureNoteData } from "./secure-note.data";
|
||||
import { SshKeyData } from "./ssh-key.data";
|
||||
|
||||
export class CipherData {
|
||||
id: string;
|
||||
@@ -28,6 +29,7 @@ export class CipherData {
|
||||
secureNote?: SecureNoteData;
|
||||
card?: CardData;
|
||||
identity?: IdentityData;
|
||||
sshKey?: SshKeyData;
|
||||
fields?: FieldData[];
|
||||
attachments?: AttachmentData[];
|
||||
passwordHistory?: PasswordHistoryData[];
|
||||
@@ -72,6 +74,9 @@ export class CipherData {
|
||||
case CipherType.Identity:
|
||||
this.identity = new IdentityData(response.identity);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
this.sshKey = new SshKeyData(response.sshKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
17
libs/common/src/vault/models/data/ssh-key.data.ts
Normal file
17
libs/common/src/vault/models/data/ssh-key.data.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SshKeyApi } from "../api/ssh-key.api";
|
||||
|
||||
export class SshKeyData {
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
keyFingerprint: string;
|
||||
|
||||
constructor(data?: SshKeyApi) {
|
||||
if (data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.privateKey = data.privateKey;
|
||||
this.publicKey = data.publicKey;
|
||||
this.keyFingerprint = data.keyFingerprint;
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import { Identity } from "./identity";
|
||||
import { Login } from "./login";
|
||||
import { Password } from "./password";
|
||||
import { SecureNote } from "./secure-note";
|
||||
import { SshKey } from "./ssh-key";
|
||||
|
||||
export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
readonly initializerKey = InitializerKey.Cipher;
|
||||
@@ -39,6 +40,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
identity: Identity;
|
||||
card: Card;
|
||||
secureNote: SecureNote;
|
||||
sshKey: SshKey;
|
||||
attachments: Attachment[];
|
||||
fields: Field[];
|
||||
passwordHistory: Password[];
|
||||
@@ -97,6 +99,9 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
case CipherType.Identity:
|
||||
this.identity = new Identity(obj.identity);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
this.sshKey = new SshKey(obj.sshKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -156,6 +161,9 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
case CipherType.Identity:
|
||||
model.identity = await this.identity.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -240,6 +248,9 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
case CipherType.Identity:
|
||||
c.identity = this.identity.toIdentityData();
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
c.sshKey = this.sshKey.toSshKeyData();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -295,6 +306,9 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
|
||||
case CipherType.SecureNote:
|
||||
domain.secureNote = SecureNote.fromJSON(obj.secureNote);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
domain.sshKey = SshKey.fromJSON(obj.sshKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
67
libs/common/src/vault/models/domain/ssh-key.spec.ts
Normal file
67
libs/common/src/vault/models/domain/ssh-key.spec.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { mockEnc } from "../../../../spec";
|
||||
import { SshKeyApi } from "../api/ssh-key.api";
|
||||
import { SshKeyData } from "../data/ssh-key.data";
|
||||
|
||||
import { SshKey } from "./ssh-key";
|
||||
|
||||
describe("Sshkey", () => {
|
||||
let data: SshKeyData;
|
||||
|
||||
beforeEach(() => {
|
||||
data = new SshKeyData(
|
||||
new SshKeyApi({
|
||||
PrivateKey: "privateKey",
|
||||
PublicKey: "publicKey",
|
||||
KeyFingerprint: "keyFingerprint",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("Convert", () => {
|
||||
const sshKey = new SshKey(data);
|
||||
|
||||
expect(sshKey).toEqual({
|
||||
privateKey: { encryptedString: "privateKey", encryptionType: 0 },
|
||||
publicKey: { encryptedString: "publicKey", encryptionType: 0 },
|
||||
keyFingerprint: { encryptedString: "keyFingerprint", encryptionType: 0 },
|
||||
});
|
||||
});
|
||||
|
||||
it("Convert from empty", () => {
|
||||
const data = new SshKeyData();
|
||||
const sshKey = new SshKey(data);
|
||||
|
||||
expect(sshKey).toEqual({
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
});
|
||||
});
|
||||
|
||||
it("toSshKeyData", () => {
|
||||
const sshKey = new SshKey(data);
|
||||
expect(sshKey.toSshKeyData()).toEqual(data);
|
||||
});
|
||||
|
||||
it("Decrypt", async () => {
|
||||
const sshKey = Object.assign(new SshKey(), {
|
||||
privateKey: mockEnc("privateKey"),
|
||||
publicKey: mockEnc("publicKey"),
|
||||
keyFingerprint: mockEnc("keyFingerprint"),
|
||||
});
|
||||
const expectedView = {
|
||||
privateKey: "privateKey",
|
||||
publicKey: "publicKey",
|
||||
keyFingerprint: "keyFingerprint",
|
||||
};
|
||||
|
||||
const loginView = await sshKey.decrypt(null);
|
||||
expect(loginView).toEqual(expectedView);
|
||||
});
|
||||
|
||||
describe("fromJSON", () => {
|
||||
it("returns null if object is null", () => {
|
||||
expect(SshKey.fromJSON(null)).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
70
libs/common/src/vault/models/domain/ssh-key.ts
Normal file
70
libs/common/src/vault/models/domain/ssh-key.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
||||
|
||||
import Domain from "../../../platform/models/domain/domain-base";
|
||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||
import { SshKeyData } from "../data/ssh-key.data";
|
||||
import { SshKeyView } from "../view/ssh-key.view";
|
||||
|
||||
export class SshKey extends Domain {
|
||||
privateKey: EncString;
|
||||
publicKey: EncString;
|
||||
keyFingerprint: EncString;
|
||||
|
||||
constructor(obj?: SshKeyData) {
|
||||
super();
|
||||
if (obj == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.buildDomainModel(
|
||||
this,
|
||||
obj,
|
||||
{
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<SshKeyView> {
|
||||
return this.decryptObj(
|
||||
new SshKeyView(),
|
||||
{
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
},
|
||||
orgId,
|
||||
encKey,
|
||||
);
|
||||
}
|
||||
|
||||
toSshKeyData(): SshKeyData {
|
||||
const c = new SshKeyData();
|
||||
this.buildDataModel(this, c, {
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
});
|
||||
return c;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<SshKey>>): SshKey {
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const privateKey = EncString.fromJSON(obj.privateKey);
|
||||
const publicKey = EncString.fromJSON(obj.publicKey);
|
||||
const keyFingerprint = EncString.fromJSON(obj.keyFingerprint);
|
||||
return Object.assign(new SshKey(), obj, {
|
||||
privateKey,
|
||||
publicKey,
|
||||
keyFingerprint,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { IdentityApi } from "../api/identity.api";
|
||||
import { LoginUriApi } from "../api/login-uri.api";
|
||||
import { LoginApi } from "../api/login.api";
|
||||
import { SecureNoteApi } from "../api/secure-note.api";
|
||||
import { SshKeyApi } from "../api/ssh-key.api";
|
||||
import { Cipher } from "../domain/cipher";
|
||||
|
||||
import { AttachmentRequest } from "./attachment.request";
|
||||
@@ -23,6 +24,7 @@ export class CipherRequest {
|
||||
secureNote: SecureNoteApi;
|
||||
card: CardApi;
|
||||
identity: IdentityApi;
|
||||
sshKey: SshKeyApi;
|
||||
fields: FieldApi[];
|
||||
passwordHistory: PasswordHistoryRequest[];
|
||||
// Deprecated, remove at some point and rename attachments2 to attachments
|
||||
@@ -93,6 +95,17 @@ export class CipherRequest {
|
||||
this.secureNote = new SecureNoteApi();
|
||||
this.secureNote.type = cipher.secureNote.type;
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
this.sshKey = new SshKeyApi();
|
||||
this.sshKey.privateKey =
|
||||
cipher.sshKey.privateKey != null ? cipher.sshKey.privateKey.encryptedString : null;
|
||||
this.sshKey.publicKey =
|
||||
cipher.sshKey.publicKey != null ? cipher.sshKey.publicKey.encryptedString : null;
|
||||
this.sshKey.keyFingerprint =
|
||||
cipher.sshKey.keyFingerprint != null
|
||||
? cipher.sshKey.keyFingerprint.encryptedString
|
||||
: null;
|
||||
break;
|
||||
case CipherType.Card:
|
||||
this.card = new CardApi();
|
||||
this.card.cardholderName =
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FieldApi } from "../api/field.api";
|
||||
import { IdentityApi } from "../api/identity.api";
|
||||
import { LoginApi } from "../api/login.api";
|
||||
import { SecureNoteApi } from "../api/secure-note.api";
|
||||
import { SshKeyApi } from "../api/ssh-key.api";
|
||||
|
||||
import { AttachmentResponse } from "./attachment.response";
|
||||
import { PasswordHistoryResponse } from "./password-history.response";
|
||||
@@ -21,6 +22,7 @@ export class CipherResponse extends BaseResponse {
|
||||
card: CardApi;
|
||||
identity: IdentityApi;
|
||||
secureNote: SecureNoteApi;
|
||||
sshKey: SshKeyApi;
|
||||
favorite: boolean;
|
||||
edit: boolean;
|
||||
viewPassword: boolean;
|
||||
@@ -75,6 +77,11 @@ export class CipherResponse extends BaseResponse {
|
||||
this.secureNote = new SecureNoteApi(secureNote);
|
||||
}
|
||||
|
||||
const sshKey = this.getResponseProperty("sshKey");
|
||||
if (sshKey != null) {
|
||||
this.sshKey = new SshKeyApi(sshKey);
|
||||
}
|
||||
|
||||
const fields = this.getResponseProperty("Fields");
|
||||
if (fields != null) {
|
||||
this.fields = fields.map((f: any) => new FieldApi(f));
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IdentityView } from "./identity.view";
|
||||
import { LoginView } from "./login.view";
|
||||
import { PasswordHistoryView } from "./password-history.view";
|
||||
import { SecureNoteView } from "./secure-note.view";
|
||||
import { SshKeyView } from "./ssh-key.view";
|
||||
|
||||
export class CipherView implements View, InitializerMetadata {
|
||||
readonly initializerKey = InitializerKey.CipherView;
|
||||
@@ -33,6 +34,7 @@ export class CipherView implements View, InitializerMetadata {
|
||||
identity = new IdentityView();
|
||||
card = new CardView();
|
||||
secureNote = new SecureNoteView();
|
||||
sshKey = new SshKeyView();
|
||||
attachments: AttachmentView[] = null;
|
||||
fields: FieldView[] = null;
|
||||
passwordHistory: PasswordHistoryView[] = null;
|
||||
@@ -74,6 +76,8 @@ export class CipherView implements View, InitializerMetadata {
|
||||
return this.card;
|
||||
case CipherType.Identity:
|
||||
return this.identity;
|
||||
case CipherType.SshKey:
|
||||
return this.sshKey;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -190,6 +194,9 @@ export class CipherView implements View, InitializerMetadata {
|
||||
case CipherType.SecureNote:
|
||||
view.secureNote = SecureNoteView.fromJSON(obj.secureNote);
|
||||
break;
|
||||
case CipherType.SshKey:
|
||||
view.sshKey = SshKeyView.fromJSON(obj.sshKey);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
41
libs/common/src/vault/models/view/ssh-key.view.ts
Normal file
41
libs/common/src/vault/models/view/ssh-key.view.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { Jsonify } from "type-fest";
|
||||
|
||||
import { SshKey } from "../domain/ssh-key";
|
||||
|
||||
import { ItemView } from "./item.view";
|
||||
|
||||
export class SshKeyView extends ItemView {
|
||||
privateKey: string = null;
|
||||
publicKey: string = null;
|
||||
keyFingerprint: string = null;
|
||||
|
||||
constructor(n?: SshKey) {
|
||||
super();
|
||||
if (!n) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
get maskedPrivateKey(): string {
|
||||
let lines = this.privateKey.split("\n").filter((l) => l.trim() !== "");
|
||||
lines = lines.map((l, i) => {
|
||||
if (i === 0 || i === lines.length - 1) {
|
||||
return l;
|
||||
}
|
||||
return this.maskLine(l);
|
||||
});
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
private maskLine(line: string): string {
|
||||
return "•".repeat(32);
|
||||
}
|
||||
|
||||
get subTitle(): string {
|
||||
return this.keyFingerprint;
|
||||
}
|
||||
|
||||
static fromJSON(obj: Partial<Jsonify<SshKeyView>>): SshKeyView {
|
||||
return Object.assign(new SshKeyView(), obj);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import { LoginUri } from "../models/domain/login-uri";
|
||||
import { Password } from "../models/domain/password";
|
||||
import { SecureNote } from "../models/domain/secure-note";
|
||||
import { SortedCiphersCache } from "../models/domain/sorted-ciphers-cache";
|
||||
import { SshKey } from "../models/domain/ssh-key";
|
||||
import { CipherBulkDeleteRequest } from "../models/request/cipher-bulk-delete.request";
|
||||
import { CipherBulkMoveRequest } from "../models/request/cipher-bulk-move.request";
|
||||
import { CipherBulkRestoreRequest } from "../models/request/cipher-bulk-restore.request";
|
||||
@@ -1570,6 +1571,19 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
key,
|
||||
);
|
||||
return;
|
||||
case CipherType.SshKey:
|
||||
cipher.sshKey = new SshKey();
|
||||
await this.encryptObjProperty(
|
||||
model.sshKey,
|
||||
cipher.sshKey,
|
||||
{
|
||||
privateKey: null,
|
||||
publicKey: null,
|
||||
keyFingerprint: null,
|
||||
},
|
||||
key,
|
||||
);
|
||||
return;
|
||||
default:
|
||||
throw new Error("Unknown cipher type.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user