Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to hybrid enc in InviteDialogContainer #1422

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/**
* @jest-environment jsdom
*/
import { describe, expect, it, vi, beforeEach, beforeAll } from 'vitest';
import { processInvitation } from './InviteDialogContainer';
import {
generateNewKeys,
hybridDecryptMessageWithPrivateKey,
decryptMessageWithPrivateKey,
} from '../../../../../crypto/services/pgp.service';
import navigationService from '../../../../../core/services/navigation.service';
import workspacesService from '../../../../../core/services/workspace.service';
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';
import userService from '../../../../../auth/services/user.service';
import { Buffer } from 'buffer';

describe('Encryption and Decryption', () => {
beforeAll(() => {
vi.mock('../../../../../core/services/navigation.service', () => ({
default: { push: vi.fn() },
}));
vi.mock('../../../../../core/services/workspace.service', () => ({
default: {
inviteUserToTeam: vi.fn(),
},
}));
vi.mock('../../../../../auth/services/user.service', () => ({
default: {
getPublicKeyByEmail: vi.fn(),
preCreateUser: vi.fn(),
},
}));

vi.mock('../../../../../store', () => ({
RootState: vi.fn(),
}));
vi.mock('../InviteDialog', () => ({
default: {
UserInviteDialog: vi.fn(),
},
}));
vi.mock('react-redux', () => ({
useSelector: vi.fn(),
}));
vi.mock('../../../../../core/types', () => ({
AppView: vi.fn(),
}));

vi.mock('../../../../../notifications/services/notifications.service', () => ({
default: {
show: vi.fn(),
},
ToastType: {
Error: 'ERROR',
},
}));
vi.mock('../../../../../core/services/error.service', () => ({
default: {
castError: vi.fn().mockImplementation((e) => ({ message: e.message || 'Default error message' })),
reportError: vi.fn(),
},
}));
});

beforeEach(() => {
vi.clearAllMocks();
});

async function getMockUser() {
const keys = await generateNewKeys();
const mockUser: Partial<UserSettings> = {
uuid: 'mock-uuid',
email: '[email protected]',
mnemonic:
'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state',
keys: {
ecc: {
publicKey: keys.publicKeyArmored,
privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'),
},
kyber: {
publicKey: keys.publicKyberKeyBase64,
privateKey: keys.privateKyberKeyBase64,
},
},
};
return {
user: mockUser as UserSettings,
privateKey: keys.privateKeyArmored,
privateKyberKey: keys.privateKyberKeyBase64,
};
}

async function getMockOldUser() {
const keys = await generateNewKeys();
const mockUser: Partial<UserSettings> = {
uuid: 'mock-uuid',
email: '[email protected]',
mnemonic:
'truck arch rather sell tilt return warm nurse rack vacuum rubber tribe unfold scissors copper sock panel ozone harsh ahead danger soda legal state',
publicKey: keys.publicKeyArmored,
privateKey: Buffer.from(keys.privateKeyArmored).toString('base64'),
};
return {
user: mockUser as UserSettings,
privateKey: keys.privateKeyArmored,
};
}

it('should process invitation and encrypt mnemonic with kyber for existing user', async () => {
const { user, privateKey, privateKyberKey } = await getMockUser();
const mockEmail = user.email;
const mockWorkspaceId = 'mock-workspaceId';
const mockMessageText = 'mock-messageText';

const mockWorkspacesService = {
inviteUserToTeam: vi.fn(),
};

vi.spyOn(navigationService, 'push').mockImplementation(() => {});
vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam);
vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(
Promise.resolve({ publicKey: user.keys.ecc.publicKey, publicKyberKey: user.keys?.kyber.publicKey }),
);

await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText);

const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0];
expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined();

const { encryptedMnemonicInBase64 } = workspacesServiceInfo;
const decryptedMessage = await hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64: encryptedMnemonicInBase64,
privateKeyInBase64: Buffer.from(privateKey).toString('base64'),
privateKyberKeyInBase64: privateKyberKey,
});

expect(decryptedMessage).toEqual(user.mnemonic);
});

it('should process invitation and encrypt mnemonic without kyber for existing user', async () => {
const { user, privateKey } = await getMockOldUser();
const mockEmail = user.email;
const mockWorkspaceId = 'mock-workspaceId';
const mockMessageText = 'mock-messageText';

const mockWorkspacesService = {
inviteUserToTeam: vi.fn(),
};

vi.spyOn(navigationService, 'push').mockImplementation(() => {});
vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam);
vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: user.publicKey }));

await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText);

const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0];
expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined();

const { encryptedMnemonicInBase64 } = workspacesServiceInfo;
const decryptedMessage = await decryptMessageWithPrivateKey({
encryptedMessage: atob(encryptedMnemonicInBase64),
privateKeyInBase64: Buffer.from(privateKey).toString('base64'),
});

expect(decryptedMessage).toEqual(user.mnemonic);
});

it('should process invitation and encrypt mnemonic with kyber for new user', async () => {
const { user, privateKey, privateKyberKey } = await getMockUser();
const mockEmail = user.email;
const mockWorkspaceId = 'mock-workspaceId';
const mockMessageText = 'mock-messageText';

const mockWorkspacesService = {
inviteUserToTeam: vi.fn(),
};

vi.spyOn(navigationService, 'push').mockImplementation(() => {});
vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam);
vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(
Promise.resolve({ publicKey: '', publicKyberKey: '' }),
);
vi.spyOn(userService, 'preCreateUser').mockReturnValue(
Promise.resolve({
publicKey: user.keys.ecc.publicKey,
publicKyberKey: user.keys?.kyber.publicKey,
user: {
uuid: user.userId,
email: user.email,
},
}),
);

await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText);

const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0];
expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined();

const { encryptedMnemonicInBase64 } = workspacesServiceInfo;
const decryptedMessage = await hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64: encryptedMnemonicInBase64,
privateKeyInBase64: Buffer.from(privateKey).toString('base64'),
privateKyberKeyInBase64: privateKyberKey,
});

expect(decryptedMessage).toEqual(user.mnemonic);
});

it('should process invitation and encrypt mnemonic for new user without Kyber', async () => {
const { user, privateKey } = await getMockOldUser();
const mockEmail = user.email;
const mockWorkspaceId = 'mock-workspaceId';
const mockMessageText = 'mock-messageText';

const mockWorkspacesService = {
inviteUserToTeam: vi.fn(),
};

vi.spyOn(navigationService, 'push').mockImplementation(() => {});
vi.spyOn(workspacesService, 'inviteUserToTeam').mockImplementation(mockWorkspacesService.inviteUserToTeam);
vi.spyOn(userService, 'getPublicKeyByEmail').mockReturnValue(Promise.resolve({ publicKey: '' }));
vi.spyOn(userService, 'preCreateUser').mockReturnValue(
Promise.resolve({
publicKey: user.publicKey,
user: {
uuid: user.userId,
email: user.email,
},
}),
);

await processInvitation(user, mockEmail, mockWorkspaceId, mockMessageText);

const [workspacesServiceInfo] = mockWorkspacesService.inviteUserToTeam.mock.calls[0];
expect(workspacesServiceInfo.encryptedMnemonicInBase64).toBeDefined();

const { encryptedMnemonicInBase64 } = workspacesServiceInfo;
const decryptedMessage = await hybridDecryptMessageWithPrivateKey({
encryptedMessageInBase64: encryptedMnemonicInBase64,
privateKeyInBase64: Buffer.from(privateKey).toString('base64'),
privateKyberKeyInBase64: '',
});

expect(decryptedMessage).toEqual(user.mnemonic);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import errorService from '../../../../../core/services/error.service';
import navigationService from '../../../../../core/services/navigation.service';
import workspacesService from '../../../../../core/services/workspace.service';
import { AppView } from '../../../../../core/types';
import { encryptMessageWithPublicKey } from '../../../../../crypto/services/pgp.service';
import { hybridEncryptMessageWithPublicKey } from '../../../../../crypto/services/pgp.service';
import notificationsService, { ToastType } from '../../../../../notifications/services/notifications.service';
import { RootState } from '../../../../../store';
import UserInviteDialog from '../InviteDialog';
Expand All @@ -28,7 +28,7 @@ const InviteDialogContainer = ({ isOpen, onClose }) => {
return <UserInviteDialog isOpen={isOpen} onClose={onClose} processInvitation={processWorkspaceInvitation} />;
};

const processInvitation = async (
export const processInvitation = async (
user: UserSettings | null,
email: string,
workspaceId: string,
Expand All @@ -40,10 +40,11 @@ const processInvitation = async (
return;
}
const { mnemonic } = user;
let publicKey;
let publicKey, publicKyberKey;
try {
const publicKeyResponse = await userService.getPublicKeyByEmail(email);
publicKey = publicKeyResponse.publicKey;
publicKyberKey = publicKeyResponse.publicKyberKey ?? '';
} catch (err) {
console.log(err);
}
Expand All @@ -52,14 +53,15 @@ const processInvitation = async (
if (isNewUser) {
const preCreatedUserResponse = await userService.preCreateUser(email);
publicKey = preCreatedUserResponse.publicKey;
publicKyberKey = preCreatedUserResponse.publicKyberKey ?? '';
}

const encryptedMnemonic = await encryptMessageWithPublicKey({
const encryptedMnemonicInBase64 = await hybridEncryptMessageWithPublicKey({
message: mnemonic,
publicKeyInBase64: publicKey,
publicKyberKeyBase64: publicKyberKey,
});

const encryptedMnemonicInBase64 = btoa(encryptedMnemonic as string);
await workspacesService.inviteUserToTeam({
workspaceId: workspaceId,
invitedUserEmail: email,
Expand Down
3 changes: 3 additions & 0 deletions src/app/share/services/share.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -869,6 +869,9 @@ const shareService = {
validateSharingInvitation,
getPublicSharedItemInfo,
getSharedFolderSize,
inviteUserToSharedFolder,
getSharedFolderInvitationsAsInvitedUser,
getSharingRoles,
};

export default shareService;
Loading