mirror of
https://github.com/remotely-save/remotely-save.git
synced 2024-06-07 21:10:45 +00:00
half way of encryption refactor
This commit is contained in:
parent
98380b6c92
commit
6825241071
@ -58,6 +58,7 @@
|
||||
"@aws-sdk/signature-v4-crt": "^3.474.0",
|
||||
"@aws-sdk/types": "^3.468.0",
|
||||
"@azure/msal-node": "^2.6.0",
|
||||
"@fyears/rclone-crypt": "^0.0.6",
|
||||
"@fyears/tsqueue": "^1.0.1",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
"@smithy/fetch-http-handler": "^2.3.1",
|
||||
|
@ -88,6 +88,8 @@ export type SyncDirectionType =
|
||||
| "incremental_pull_only"
|
||||
| "incremental_push_only";
|
||||
|
||||
export type CipherMethodType = "rclone-base64" | "openssl-base64" | "unknown";
|
||||
|
||||
export interface RemotelySavePluginSettings {
|
||||
s3: S3Config;
|
||||
webdav: WebdavConfig;
|
||||
@ -119,6 +121,8 @@ export interface RemotelySavePluginSettings {
|
||||
|
||||
enableMobileStatusBar?: boolean;
|
||||
|
||||
encryptionMethod?: CipherMethodType;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
|
122
src/encryptUnified.ts
Normal file
122
src/encryptUnified.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { CipherMethodType } from "./baseTypes";
|
||||
import * as openssl from "./encryptOpenSSL";
|
||||
import { isVaildText } from "./misc";
|
||||
|
||||
export class Cipher {
|
||||
readonly password: string;
|
||||
readonly method: CipherMethodType;
|
||||
constructor(password: string, method: CipherMethodType) {
|
||||
this.password = password ?? "";
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
isPasswordEmpty() {
|
||||
return this.password === "";
|
||||
}
|
||||
|
||||
async encryptContent(content: ArrayBuffer) {
|
||||
if (this.password === "") {
|
||||
return content;
|
||||
}
|
||||
if (this.method === "openssl-base64") {
|
||||
return await openssl.encryptArrayBuffer(content, this.password);
|
||||
} else if (this.method === "rclone-base64") {
|
||||
throw Error("not implemented yet");
|
||||
} else {
|
||||
throw Error(`not supported encrypt method=${this.method}`);
|
||||
}
|
||||
}
|
||||
|
||||
async decryptContent(content: ArrayBuffer) {
|
||||
if (this.password === "") {
|
||||
return content;
|
||||
}
|
||||
if (this.method === "openssl-base64") {
|
||||
return await openssl.decryptArrayBuffer(content, this.password);
|
||||
} else if (this.method === "rclone-base64") {
|
||||
throw Error("not implemented yet");
|
||||
} else {
|
||||
throw Error(`not supported encrypt method=${this.method}`);
|
||||
}
|
||||
}
|
||||
|
||||
async encryptName(name: string) {
|
||||
if (this.password === "") {
|
||||
return name;
|
||||
}
|
||||
if (this.method === "openssl-base64") {
|
||||
return await openssl.encryptStringToBase64url(name, this.password);
|
||||
} else if (this.method === "rclone-base64") {
|
||||
throw Error("not implemented yet");
|
||||
} else {
|
||||
throw Error(`not supported encrypt method=${this.method}`);
|
||||
}
|
||||
}
|
||||
|
||||
async decryptName(name: string) {
|
||||
if (this.password === "") {
|
||||
return name;
|
||||
}
|
||||
if (this.method === "openssl-base64") {
|
||||
if (name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE32)) {
|
||||
// backward compitable with the openssl-base32
|
||||
try {
|
||||
const res = await openssl.decryptBase32ToString(name, this.password);
|
||||
if (isVaildText(res)) {
|
||||
return res;
|
||||
} else {
|
||||
throw Error(`cannot decrypt name=${name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw Error(`cannot decrypt name=${name}`);
|
||||
}
|
||||
} else if (name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE64URL)) {
|
||||
try {
|
||||
const res = await openssl.decryptBase64urlToString(
|
||||
name,
|
||||
this.password
|
||||
);
|
||||
if (isVaildText(res)) {
|
||||
return res;
|
||||
} else {
|
||||
throw Error(`cannot decrypt name=${name}`);
|
||||
}
|
||||
} catch (error) {
|
||||
throw Error(`cannot decrypt name=${name}`);
|
||||
}
|
||||
}
|
||||
} else if (this.method === "rclone-base64") {
|
||||
throw Error("not implemented yet");
|
||||
} else {
|
||||
throw Error(`not supported encrypt method=${this.method}`);
|
||||
}
|
||||
}
|
||||
|
||||
getSizeFromOrigToEnc(x: number) {
|
||||
if (this.password === "") {
|
||||
return x;
|
||||
}
|
||||
if (this.method === "openssl-base64") {
|
||||
return openssl.getSizeFromOrigToEnc(x);
|
||||
} else if (this.method === "rclone-base64") {
|
||||
throw Error("not implemented yet");
|
||||
} else {
|
||||
throw Error(`not supported encrypt method=${this.method}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* quick guess, no actual decryption here
|
||||
* @param name
|
||||
* @returns
|
||||
*/
|
||||
static isLikelyEncryptedName(name: string): boolean {
|
||||
if (
|
||||
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE32) ||
|
||||
name.startsWith(openssl.MAGIC_ENCRYPTED_PREFIX_BASE64URL)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -109,7 +109,12 @@
|
||||
"modal_sizesconflict_copynotice": "All the sizes conflicts info have been copied to the clipboard!",
|
||||
"settings_basic": "Basic Settings",
|
||||
"settings_password": "Encryption Password",
|
||||
"settings_password_desc": "Password for E2E encryption. Empty for no password. You need to click \"Confirm\". Attention: the password and other info are saved locally.",
|
||||
"settings_password_desc": "Password for E2E encryption. Empty for no password. You need to click \"Confirm\". Attention: The password and other info are saved locally. After changing the password, you need to manually delete every original files in the remote, and re-sync (so that upload) the encrypted files again.",
|
||||
"settings_encryptionmethod": "Encryption Method",
|
||||
"settings_encryptionmethod_desc": "Encryption method for E2E encryption. RClone method is recommended but it doesn't encrypt path structure. OpenSSL is the legacy method of this plugin. Attention: After switching the method, you need to manually delete every original files in the remote and re-sync (so that upload) the encrypted files again.",
|
||||
"settings_encryptionmethod_rclone": "RClone (recommended)",
|
||||
"settings_encryptionmethod_openssl": "OpenSSL (legacy)",
|
||||
|
||||
"settings_autorun": "Schedule For Auto Run",
|
||||
"settings_autorun_desc": "The plugin tries to schedule the running after every interval. Battery may be impacted.",
|
||||
"settings_autorun_notset": "(not set)",
|
||||
|
@ -109,7 +109,11 @@
|
||||
"modal_sizesconflict_copynotice": "所有的文件大小冲突信息,已被复制到剪贴板!",
|
||||
"settings_basic": "基本设置",
|
||||
"settings_password": "密码",
|
||||
"settings_password_desc": "端到端加密的密码。不填写则代表没密码。您需要点击“确认”来修改。注意:密码和其它信息都会在本地保存。",
|
||||
"settings_password_desc": "端到端加密的密码。不填写则代表没密码。您需要点击“确认”来修改。注意:密码和其它信息都会在本地保存。如果您修改了密码,您需要手动删除远端的所有文件,重新同步(从而上传)加密文件。",
|
||||
"settings_encryptionmethod": "加密方法",
|
||||
"settings_encryptionmethod_desc": "端到端加密的方法。推荐选用 RClone 方法,但是它没有加密文件路径结构。OpenSSL 是本插件一开始就支持的方式。如果您修改了加密方法您需要手动删除远端的所有文件,重新同步(从而上传)加密文件。",
|
||||
"settings_encryptionmethod_rclone": "RClone(推荐)",
|
||||
"settings_encryptionmethod_openssl": "OpenSSL(旧方法)",
|
||||
"settings_autorun": "自动运行",
|
||||
"settings_autorun_desc": "每隔一段时间,此插件尝试自动同步。会影响到电池用量。",
|
||||
"settings_autorun_notset": "(不设置)",
|
||||
|
@ -109,7 +109,11 @@
|
||||
"modal_sizesconflict_copynotice": "所有的檔案大小衝突資訊,已被複制到剪貼簿!",
|
||||
"settings_basic": "基本設定",
|
||||
"settings_password": "密碼",
|
||||
"settings_password_desc": "端到端加密的密碼。不填寫則代表沒密碼。您需要點選“確認”來修改。注意:密碼和其它資訊都會在本地儲存。",
|
||||
"settings_password_desc": "端到端加密的密碼。不填寫則代表沒密碼。您需要點選“確認”來修改。注意:密碼和其它資訊都會在本地儲存。如果您修改了密碼,您需要手動刪除遠端的所有檔案,重新同步(從而上傳)加密檔案。",
|
||||
"settings_encryptionmethod": "加密方法",
|
||||
"settings_encryptionmethod_desc": "端到端加密的方法。推薦選用 RClone 方法,但是它沒有加密檔案路徑結構。OpenSSL 是本外掛一開始就支援的方式。如果您修改了加密方法您需要手動刪除遠端的所有檔案,重新同步(從而上傳)加密檔案。",
|
||||
"settings_encryptionmethod_rclone": "RClone(推薦)",
|
||||
"settings_encryptionmethod_openssl": "OpenSSL(舊方法)",
|
||||
"settings_autorun": "自動執行",
|
||||
"settings_autorun_desc": "每隔一段時間,此外掛嘗試自動同步。會影響到電池用量。",
|
||||
"settings_autorun_notset": "(不設定)",
|
||||
|
30
src/main.ts
30
src/main.ts
@ -67,6 +67,7 @@ import { SyncAlgoV3Modal } from "./syncAlgoV3Notice";
|
||||
import AggregateError from "aggregate-error";
|
||||
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
||||
import { changeMobileStatusBar, compareVersion } from "./misc";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||
s3: DEFAULT_S3_CONFIG,
|
||||
@ -97,6 +98,7 @@ const DEFAULT_SETTINGS: RemotelySavePluginSettings = {
|
||||
syncDirection: "bidirectional",
|
||||
obfuscateSettingFile: true,
|
||||
enableMobileStatusBar: false,
|
||||
encryptionMethod: "unknown",
|
||||
};
|
||||
|
||||
interface OAuth2Info {
|
||||
@ -254,10 +256,12 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
getNotice(t("syncrun_step3"));
|
||||
}
|
||||
this.syncStatus = "checking_password";
|
||||
const passwordCheckResult = await isPasswordOk(
|
||||
remoteEntityList,
|
||||
this.settings.password
|
||||
|
||||
const cipher = new Cipher(
|
||||
this.settings.password,
|
||||
this.settings.encryptionMethod ?? "unknown"
|
||||
);
|
||||
const passwordCheckResult = await isPasswordOk(remoteEntityList, cipher);
|
||||
if (!passwordCheckResult.ok) {
|
||||
getNotice(t("syncrun_passworderr"));
|
||||
throw Error(passwordCheckResult.reason);
|
||||
@ -306,7 +310,7 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
this.app.vault.configDir,
|
||||
this.settings.syncUnderscoreItems ?? false,
|
||||
this.settings.ignorePaths ?? [],
|
||||
this.settings.password,
|
||||
cipher,
|
||||
this.settings.serviceType
|
||||
);
|
||||
mixedEntityMappings = await getSyncPlanInplace(
|
||||
@ -341,7 +345,7 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
this.vaultRandomID,
|
||||
profileID,
|
||||
this.app.vault,
|
||||
this.settings.password,
|
||||
cipher,
|
||||
this.settings.concurrency ?? 5,
|
||||
(key: string) => self.trash(key),
|
||||
this.settings.protectModifyPercentage ?? 50,
|
||||
@ -911,6 +915,22 @@ export default class RemotelySavePlugin extends Plugin {
|
||||
this.settings.enableMobileStatusBar = false;
|
||||
}
|
||||
|
||||
if (
|
||||
this.settings.encryptionMethod === undefined ||
|
||||
this.settings.encryptionMethod === "unknown"
|
||||
) {
|
||||
if (
|
||||
this.settings.password === undefined ||
|
||||
this.settings.password === ""
|
||||
) {
|
||||
// we have a preferred way
|
||||
this.settings.encryptionMethod = "rclone-base64";
|
||||
} else {
|
||||
// likely to be inherited from the old version
|
||||
this.settings.encryptionMethod = "openssl-base64";
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveSettings();
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import * as dropbox from "./remoteForDropbox";
|
||||
import * as onedrive from "./remoteForOnedrive";
|
||||
import * as s3 from "./remoteForS3";
|
||||
import * as webdav from "./remoteForWebdav";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
export class RemoteClient {
|
||||
readonly serviceType: SUPPORTED_SERVICES_TYPE;
|
||||
@ -105,8 +106,8 @@ export class RemoteClient {
|
||||
uploadToRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault | undefined,
|
||||
isRecursively: boolean = false,
|
||||
password: string = "",
|
||||
isRecursively: boolean,
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
foldersCreatedBefore: Set<string> | undefined = undefined,
|
||||
uploadRaw: boolean = false,
|
||||
@ -119,7 +120,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
isRecursively,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
uploadRaw,
|
||||
rawContent
|
||||
@ -130,7 +131,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
isRecursively,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
uploadRaw,
|
||||
rawContent
|
||||
@ -141,7 +142,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
isRecursively,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
foldersCreatedBefore,
|
||||
uploadRaw,
|
||||
@ -153,7 +154,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
isRecursively,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
foldersCreatedBefore,
|
||||
uploadRaw,
|
||||
@ -185,7 +186,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault,
|
||||
mtime: number,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
skipSaving: boolean = false
|
||||
) => {
|
||||
@ -196,7 +197,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
mtime,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
skipSaving
|
||||
);
|
||||
@ -206,7 +207,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
mtime,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
skipSaving
|
||||
);
|
||||
@ -216,7 +217,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
mtime,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
skipSaving
|
||||
);
|
||||
@ -226,7 +227,7 @@ export class RemoteClient {
|
||||
fileOrFolderPath,
|
||||
vault,
|
||||
mtime,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey,
|
||||
skipSaving
|
||||
);
|
||||
@ -237,7 +238,7 @@ export class RemoteClient {
|
||||
|
||||
deleteFromRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = ""
|
||||
) => {
|
||||
if (this.serviceType === "s3") {
|
||||
@ -245,28 +246,28 @@ export class RemoteClient {
|
||||
s3.getS3Client(this.s3Config!),
|
||||
this.s3Config!,
|
||||
fileOrFolderPath,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey
|
||||
);
|
||||
} else if (this.serviceType === "webdav") {
|
||||
return await webdav.deleteFromRemote(
|
||||
this.webdavClient!,
|
||||
fileOrFolderPath,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey
|
||||
);
|
||||
} else if (this.serviceType === "dropbox") {
|
||||
return await dropbox.deleteFromRemote(
|
||||
this.dropboxClient!,
|
||||
fileOrFolderPath,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey
|
||||
);
|
||||
} else if (this.serviceType === "onedrive") {
|
||||
return await onedrive.deleteFromRemote(
|
||||
this.onedriveClient!,
|
||||
fileOrFolderPath,
|
||||
password,
|
||||
cipher,
|
||||
remoteEncryptedKey
|
||||
);
|
||||
} else {
|
||||
|
@ -10,7 +10,6 @@ import {
|
||||
OAUTH2_FORCE_EXPIRE_MILLISECONDS,
|
||||
UploadedType,
|
||||
} from "./baseTypes";
|
||||
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||
import {
|
||||
bufferToArrayBuffer,
|
||||
getFolderLevels,
|
||||
@ -18,6 +17,7 @@ import {
|
||||
headersToRecord,
|
||||
mkdirpInVault,
|
||||
} from "./misc";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
export { Dropbox } from "dropbox";
|
||||
|
||||
@ -451,8 +451,8 @@ export const uploadToRemote = async (
|
||||
client: WrappedDropboxClient,
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault | undefined,
|
||||
isRecursively: boolean = false,
|
||||
password: string = "",
|
||||
isRecursively: boolean,
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
foldersCreatedBefore: Set<string> | undefined = undefined,
|
||||
uploadRaw: boolean = false,
|
||||
@ -463,7 +463,7 @@ export const uploadToRemote = async (
|
||||
await client.init();
|
||||
|
||||
let uploadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
||||
throw Error(
|
||||
`uploadToRemote(dropbox) you have password but remoteEncryptedKey is empty!`
|
||||
@ -497,7 +497,7 @@ export const uploadToRemote = async (
|
||||
throw Error(`you specify uploadRaw, but you also provide a folder key!`);
|
||||
}
|
||||
// folder
|
||||
if (password === "") {
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
// if not encrypted, mkdir a remote folder
|
||||
if (foldersCreatedBefore?.has(uploadFile)) {
|
||||
// created, pass
|
||||
@ -564,8 +564,8 @@ export const uploadToRemote = async (
|
||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||
}
|
||||
let remoteContent = localContent;
|
||||
if (password !== "") {
|
||||
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteContent = await cipher.encryptContent(localContent);
|
||||
}
|
||||
// in dropbox, we don't need to create folders before uploading! cool!
|
||||
// TODO: filesUploadSession for larger files (>=150 MB)
|
||||
@ -670,7 +670,7 @@ export const downloadFromRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault,
|
||||
mtime: number,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
skipSaving: boolean = false
|
||||
) => {
|
||||
@ -691,14 +691,14 @@ export const downloadFromRemote = async (
|
||||
return new ArrayBuffer(0);
|
||||
} else {
|
||||
let downloadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
downloadFile = remoteEncryptedKey;
|
||||
}
|
||||
downloadFile = getDropboxPath(downloadFile, client.remoteBaseDir);
|
||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||
let localContent = remoteContent;
|
||||
if (password !== "") {
|
||||
localContent = await decryptArrayBuffer(remoteContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
localContent = await cipher.decryptContent(remoteContent);
|
||||
}
|
||||
if (!skipSaving) {
|
||||
await vault.adapter.writeBinary(fileOrFolderPath, localContent, {
|
||||
@ -712,14 +712,14 @@ export const downloadFromRemote = async (
|
||||
export const deleteFromRemote = async (
|
||||
client: WrappedDropboxClient,
|
||||
fileOrFolderPath: string,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = ""
|
||||
) => {
|
||||
if (fileOrFolderPath === "/") {
|
||||
return;
|
||||
}
|
||||
let remoteFileName = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteFileName = remoteEncryptedKey;
|
||||
}
|
||||
remoteFileName = getDropboxPath(remoteFileName, client.remoteBaseDir);
|
||||
|
@ -17,13 +17,13 @@ import {
|
||||
Entity,
|
||||
UploadedType,
|
||||
} from "./baseTypes";
|
||||
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||
import {
|
||||
bufferToArrayBuffer,
|
||||
getRandomArrayBuffer,
|
||||
getRandomIntInclusive,
|
||||
mkdirpInVault,
|
||||
} from "./misc";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
const SCOPES = ["User.Read", "Files.ReadWrite.AppFolder", "offline_access"];
|
||||
const REDIRECT_URI = `obsidian://${COMMAND_CALLBACK_ONEDRIVE}`;
|
||||
@ -694,8 +694,8 @@ export const uploadToRemote = async (
|
||||
client: WrappedOnedriveClient,
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault | undefined,
|
||||
isRecursively: boolean = false,
|
||||
password: string = "",
|
||||
isRecursively: boolean,
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
foldersCreatedBefore: Set<string> | undefined = undefined,
|
||||
uploadRaw: boolean = false,
|
||||
@ -704,7 +704,7 @@ export const uploadToRemote = async (
|
||||
await client.init();
|
||||
|
||||
let uploadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
||||
throw Error(
|
||||
`uploadToRemote(onedrive) you have password but remoteEncryptedKey is empty!`
|
||||
@ -734,7 +734,7 @@ export const uploadToRemote = async (
|
||||
throw Error(`you specify uploadRaw, but you also provide a folder key!`);
|
||||
}
|
||||
// folder
|
||||
if (password === "") {
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
// if not encrypted, mkdir a remote folder
|
||||
if (foldersCreatedBefore?.has(uploadFile)) {
|
||||
// created, pass
|
||||
@ -770,9 +770,8 @@ export const uploadToRemote = async (
|
||||
1,
|
||||
65536 /* max allowed */
|
||||
);
|
||||
const arrBufRandom = await encryptArrayBuffer(
|
||||
getRandomArrayBuffer(byteLengthRandom),
|
||||
password
|
||||
const arrBufRandom = await cipher.encryptContent(
|
||||
getRandomArrayBuffer(byteLengthRandom)
|
||||
);
|
||||
|
||||
// an encrypted folder is always small, we just use put here
|
||||
@ -816,8 +815,8 @@ export const uploadToRemote = async (
|
||||
localContent = await vault.adapter.readBinary(fileOrFolderPath);
|
||||
}
|
||||
let remoteContent = localContent;
|
||||
if (password !== "") {
|
||||
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteContent = await cipher.encryptContent(localContent);
|
||||
}
|
||||
|
||||
// no need to create parent folders firstly, cool!
|
||||
@ -930,7 +929,7 @@ export const downloadFromRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault,
|
||||
mtime: number,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
skipSaving: boolean = false
|
||||
) => {
|
||||
@ -948,14 +947,14 @@ export const downloadFromRemote = async (
|
||||
return new ArrayBuffer(0);
|
||||
} else {
|
||||
let downloadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
downloadFile = remoteEncryptedKey;
|
||||
}
|
||||
downloadFile = getOnedrivePath(downloadFile, client.remoteBaseDir);
|
||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||
let localContent = remoteContent;
|
||||
if (password !== "") {
|
||||
localContent = await decryptArrayBuffer(remoteContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
localContent = await cipher.decryptContent(remoteContent);
|
||||
}
|
||||
if (!skipSaving) {
|
||||
await vault.adapter.writeBinary(fileOrFolderPath, localContent, {
|
||||
@ -969,14 +968,14 @@ export const downloadFromRemote = async (
|
||||
export const deleteFromRemote = async (
|
||||
client: WrappedOnedriveClient,
|
||||
fileOrFolderPath: string,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = ""
|
||||
) => {
|
||||
if (fileOrFolderPath === "/") {
|
||||
return;
|
||||
}
|
||||
let remoteFileName = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteFileName = remoteEncryptedKey;
|
||||
}
|
||||
remoteFileName = getOnedrivePath(remoteFileName, client.remoteBaseDir);
|
||||
|
@ -33,7 +33,6 @@ import {
|
||||
UploadedType,
|
||||
VALID_REQURL,
|
||||
} from "./baseTypes";
|
||||
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||
import {
|
||||
arrayBufferToBuffer,
|
||||
bufferToArrayBuffer,
|
||||
@ -43,6 +42,7 @@ import {
|
||||
export { S3Client } from "@aws-sdk/client-s3";
|
||||
|
||||
import PQueue from "p-queue";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// special handler using Obsidian requestUrl
|
||||
@ -358,8 +358,8 @@ export const uploadToRemote = async (
|
||||
s3Config: S3Config,
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault | undefined,
|
||||
isRecursively: boolean = false,
|
||||
password: string = "",
|
||||
isRecursively: boolean,
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
uploadRaw: boolean = false,
|
||||
rawContent: string | ArrayBuffer = "",
|
||||
@ -368,7 +368,7 @@ export const uploadToRemote = async (
|
||||
): Promise<UploadedType> => {
|
||||
console.debug(`uploading ${fileOrFolderPath}`);
|
||||
let uploadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
||||
throw Error(
|
||||
`uploadToRemote(s3) you have password but remoteEncryptedKey is empty!`
|
||||
@ -416,7 +416,7 @@ export const uploadToRemote = async (
|
||||
// file
|
||||
// we ignore isRecursively parameter here
|
||||
let contentType = DEFAULT_CONTENT_TYPE;
|
||||
if (password === "") {
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
contentType =
|
||||
mime.contentType(
|
||||
mime.lookup(fileOrFolderPath) || DEFAULT_CONTENT_TYPE
|
||||
@ -447,8 +447,8 @@ export const uploadToRemote = async (
|
||||
}
|
||||
}
|
||||
let remoteContent = localContent;
|
||||
if (password !== "") {
|
||||
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteContent = await cipher.encryptContent(localContent);
|
||||
}
|
||||
|
||||
const bytesIn5MB = 5242880;
|
||||
@ -645,8 +645,8 @@ export const downloadFromRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault,
|
||||
mtime: number,
|
||||
password: string = "",
|
||||
remoteEncryptedKey: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string,
|
||||
skipSaving: boolean = false
|
||||
) => {
|
||||
const isFolder = fileOrFolderPath.endsWith("/");
|
||||
@ -664,7 +664,7 @@ export const downloadFromRemote = async (
|
||||
return new ArrayBuffer(0);
|
||||
} else {
|
||||
let downloadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
downloadFile = remoteEncryptedKey;
|
||||
}
|
||||
downloadFile = getRemoteWithPrefixPath(
|
||||
@ -677,8 +677,8 @@ export const downloadFromRemote = async (
|
||||
downloadFile
|
||||
);
|
||||
let localContent = remoteContent;
|
||||
if (password !== "") {
|
||||
localContent = await decryptArrayBuffer(remoteContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
localContent = await cipher.decryptContent(remoteContent);
|
||||
}
|
||||
if (!skipSaving) {
|
||||
await vault.adapter.writeBinary(fileOrFolderPath, localContent, {
|
||||
@ -700,14 +700,14 @@ export const deleteFromRemote = async (
|
||||
s3Client: S3Client,
|
||||
s3Config: S3Config,
|
||||
fileOrFolderPath: string,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = ""
|
||||
) => {
|
||||
if (fileOrFolderPath === "/") {
|
||||
return;
|
||||
}
|
||||
let remoteFileName = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteFileName = remoteEncryptedKey;
|
||||
}
|
||||
remoteFileName = getRemoteWithPrefixPath(
|
||||
@ -721,7 +721,7 @@ export const deleteFromRemote = async (
|
||||
})
|
||||
);
|
||||
|
||||
if (fileOrFolderPath.endsWith("/") && password === "") {
|
||||
if (fileOrFolderPath.endsWith("/") && cipher.isPasswordEmpty()) {
|
||||
const x = await listFromRemoteRaw(s3Client, s3Config, remoteFileName);
|
||||
x.forEach(async (element) => {
|
||||
await s3Client.send(
|
||||
@ -731,7 +731,7 @@ export const deleteFromRemote = async (
|
||||
})
|
||||
);
|
||||
});
|
||||
} else if (fileOrFolderPath.endsWith("/") && password !== "") {
|
||||
} else if (fileOrFolderPath.endsWith("/") && !cipher.isPasswordEmpty()) {
|
||||
// TODO
|
||||
} else {
|
||||
// pass
|
||||
|
@ -4,10 +4,11 @@ import { Platform, Vault, requestUrl } from "obsidian";
|
||||
import { Queue } from "@fyears/tsqueue";
|
||||
import chunk from "lodash/chunk";
|
||||
import flatten from "lodash/flatten";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
import { getReasonPhrase } from "http-status-codes";
|
||||
import { Entity, UploadedType, VALID_REQURL, WebdavConfig } from "./baseTypes";
|
||||
import { decryptArrayBuffer, encryptArrayBuffer } from "./encrypt";
|
||||
import { bufferToArrayBuffer, getPathFolder, mkdirpInVault } from "./misc";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
import type {
|
||||
FileStat,
|
||||
@ -139,7 +140,6 @@ if (VALID_REQURL) {
|
||||
|
||||
// @ts-ignore
|
||||
import { AuthType, BufferLike, createClient } from "webdav/dist/web/index.js";
|
||||
import cloneDeep from "lodash/cloneDeep";
|
||||
export type { WebDAVClient } from "webdav";
|
||||
|
||||
export const DEFAULT_WEBDAV_CONFIG = {
|
||||
@ -316,15 +316,15 @@ export const uploadToRemote = async (
|
||||
client: WrappedWebdavClient,
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault | undefined,
|
||||
isRecursively: boolean = false,
|
||||
password: string = "",
|
||||
isRecursively: boolean,
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
uploadRaw: boolean = false,
|
||||
rawContent: string | ArrayBuffer = ""
|
||||
): Promise<UploadedType> => {
|
||||
await client.init();
|
||||
let uploadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
if (remoteEncryptedKey === undefined || remoteEncryptedKey === "") {
|
||||
throw Error(
|
||||
`uploadToRemote(webdav) you have password but remoteEncryptedKey is empty!`
|
||||
@ -343,7 +343,7 @@ export const uploadToRemote = async (
|
||||
throw Error(`you specify uploadRaw, but you also provide a folder key!`);
|
||||
}
|
||||
// folder
|
||||
if (password === "") {
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
// if not encrypted, mkdir a remote folder
|
||||
await client.client.createDirectory(uploadFile, {
|
||||
recursive: true,
|
||||
@ -386,8 +386,8 @@ export const uploadToRemote = async (
|
||||
mtimeCli = (await vault.adapter.stat(fileOrFolderPath))?.mtime;
|
||||
}
|
||||
let remoteContent = localContent;
|
||||
if (password !== "") {
|
||||
remoteContent = await encryptArrayBuffer(localContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteContent = await cipher.encryptContent(localContent);
|
||||
}
|
||||
// updated 20220326: the algorithm guarantee this
|
||||
// // we need to create folders before uploading
|
||||
@ -491,7 +491,7 @@ export const downloadFromRemote = async (
|
||||
fileOrFolderPath: string,
|
||||
vault: Vault,
|
||||
mtime: number,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = "",
|
||||
skipSaving: boolean = false
|
||||
) => {
|
||||
@ -512,15 +512,15 @@ export const downloadFromRemote = async (
|
||||
return new ArrayBuffer(0);
|
||||
} else {
|
||||
let downloadFile = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
downloadFile = remoteEncryptedKey;
|
||||
}
|
||||
downloadFile = getWebdavPath(downloadFile, client.remoteBaseDir);
|
||||
// console.info(`downloadFile=${downloadFile}`);
|
||||
const remoteContent = await downloadFromRemoteRaw(client, downloadFile);
|
||||
let localContent = remoteContent;
|
||||
if (password !== "") {
|
||||
localContent = await decryptArrayBuffer(remoteContent, password);
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
localContent = await cipher.decryptContent(remoteContent);
|
||||
}
|
||||
if (!skipSaving) {
|
||||
await vault.adapter.writeBinary(fileOrFolderPath, localContent, {
|
||||
@ -534,14 +534,14 @@ export const downloadFromRemote = async (
|
||||
export const deleteFromRemote = async (
|
||||
client: WrappedWebdavClient,
|
||||
fileOrFolderPath: string,
|
||||
password: string = "",
|
||||
cipher: Cipher,
|
||||
remoteEncryptedKey: string = ""
|
||||
) => {
|
||||
if (fileOrFolderPath === "/") {
|
||||
return;
|
||||
}
|
||||
let remoteFileName = fileOrFolderPath;
|
||||
if (password !== "") {
|
||||
if (!cipher.isPasswordEmpty()) {
|
||||
remoteFileName = remoteEncryptedKey;
|
||||
}
|
||||
remoteFileName = getWebdavPath(remoteFileName, client.remoteBaseDir);
|
||||
|
@ -22,6 +22,7 @@ import {
|
||||
VALID_REQURL,
|
||||
WebdavAuthType,
|
||||
WebdavDepthType,
|
||||
CipherMethodType,
|
||||
} from "./baseTypes";
|
||||
import { exportVaultSyncPlansToFiles } from "./debugMode";
|
||||
import { exportQrCodeUri } from "./importExport";
|
||||
@ -1634,6 +1635,28 @@ export class RemotelySaveSettingTab extends PluginSettingTab {
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(basicDiv)
|
||||
.setName(t("settings_encryptionmethod"))
|
||||
.setDesc(t("settings_encryptionmethod_desc"))
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("rclone", t("settings_encryptionmethod_rclone"));
|
||||
dropdown.addOption("openssl", t("settings_encryptionmethod_openssl"));
|
||||
if (this.plugin.settings.encryptionMethod === "rclone-base64") {
|
||||
dropdown.setValue("rclone");
|
||||
} else if (this.plugin.settings.encryptionMethod === "openssl-base64") {
|
||||
dropdown.setValue("openssl");
|
||||
}
|
||||
|
||||
dropdown.onChange(async (val: string) => {
|
||||
if (val === "rclone") {
|
||||
this.plugin.settings.encryptionMethod = "rclone-base64";
|
||||
} else if (val === "openssl") {
|
||||
this.plugin.settings.encryptionMethod = "openssl-base64";
|
||||
}
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(basicDiv)
|
||||
.setName(t("settings_autorun"))
|
||||
.setDesc(t("settings_autorun_desc"))
|
||||
|
166
src/sync.ts
166
src/sync.ts
@ -1,6 +1,7 @@
|
||||
import PQueue from "p-queue";
|
||||
import XRegExp from "xregexp";
|
||||
import type {
|
||||
CipherMethodType,
|
||||
ConflictActionType,
|
||||
EmptyFolderCleanType,
|
||||
Entity,
|
||||
@ -22,14 +23,6 @@ import {
|
||||
DEFAULT_FILE_NAME_FOR_METADATAONREMOTE,
|
||||
DEFAULT_FILE_NAME_FOR_METADATAONREMOTE2,
|
||||
} from "./metadataOnRemote";
|
||||
import {
|
||||
MAGIC_ENCRYPTED_PREFIX_BASE32,
|
||||
MAGIC_ENCRYPTED_PREFIX_BASE64URL,
|
||||
decryptBase32ToString,
|
||||
decryptBase64urlToString,
|
||||
encryptStringToBase64url,
|
||||
getSizeFromOrigToEnc,
|
||||
} from "./encrypt";
|
||||
import { RemoteClient } from "./remote";
|
||||
import { Vault } from "obsidian";
|
||||
|
||||
@ -39,6 +32,7 @@ import {
|
||||
clearPrevSyncRecordByVaultAndProfile,
|
||||
upsertPrevSyncRecordByVaultAndProfile,
|
||||
} from "./localdb";
|
||||
import { Cipher } from "./encryptUnified";
|
||||
|
||||
export type SyncStatusType =
|
||||
| "idle"
|
||||
@ -55,19 +49,17 @@ export type SyncStatusType =
|
||||
export interface PasswordCheckType {
|
||||
ok: boolean;
|
||||
reason:
|
||||
| "ok"
|
||||
| "empty_remote"
|
||||
| "unknown_encryption_method"
|
||||
| "remote_encrypted_local_no_password"
|
||||
| "password_matched"
|
||||
| "password_not_matched"
|
||||
| "invalid_text_after_decryption"
|
||||
| "remote_not_encrypted_local_has_password"
|
||||
| "no_password_both_sides";
|
||||
| "password_not_matched_or_remote_not_encrypted"
|
||||
| "likely_no_password_both_sides";
|
||||
}
|
||||
|
||||
export const isPasswordOk = async (
|
||||
remote: Entity[],
|
||||
password: string = ""
|
||||
cipher: Cipher
|
||||
): Promise<PasswordCheckType> => {
|
||||
if (remote === undefined || remote.length === 0) {
|
||||
// remote empty
|
||||
@ -77,82 +69,41 @@ export const isPasswordOk = async (
|
||||
};
|
||||
}
|
||||
const santyCheckKey = remote[0].keyRaw;
|
||||
if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) {
|
||||
// this is encrypted using old base32!
|
||||
// try to decrypt it using the provided password.
|
||||
if (password === "") {
|
||||
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
// TODO: no way to distinguish remote rclone encrypted
|
||||
// if local has no password??
|
||||
if (Cipher.isLikelyEncryptedName(santyCheckKey)) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "remote_encrypted_local_no_password",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
ok: true,
|
||||
reason: "likely_no_password_both_sides",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (cipher.method === "unknown") {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "unknown_encryption_method",
|
||||
};
|
||||
}
|
||||
try {
|
||||
const res = await decryptBase32ToString(santyCheckKey, password);
|
||||
|
||||
// additional test
|
||||
// because iOS Safari bypasses decryption with wrong password!
|
||||
if (isVaildText(res)) {
|
||||
await cipher.decryptName(santyCheckKey);
|
||||
return {
|
||||
ok: true,
|
||||
reason: "password_matched",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "invalid_text_after_decryption",
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "password_not_matched",
|
||||
reason: "password_not_matched_or_remote_not_encrypted",
|
||||
};
|
||||
}
|
||||
}
|
||||
if (santyCheckKey.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE64URL)) {
|
||||
// this is encrypted using new base64url!
|
||||
// try to decrypt it using the provided password.
|
||||
if (password === "") {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "remote_encrypted_local_no_password",
|
||||
};
|
||||
}
|
||||
try {
|
||||
const res = await decryptBase64urlToString(santyCheckKey, password);
|
||||
|
||||
// additional test
|
||||
// because iOS Safari bypasses decryption with wrong password!
|
||||
if (isVaildText(res)) {
|
||||
return {
|
||||
ok: true,
|
||||
reason: "password_matched",
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "invalid_text_after_decryption",
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "password_not_matched",
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// it is not encrypted!
|
||||
if (password !== "") {
|
||||
return {
|
||||
ok: false,
|
||||
reason: "remote_not_encrypted_local_has_password",
|
||||
};
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
reason: "no_password_both_sides",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const isSkipItemByName = (
|
||||
@ -231,12 +182,9 @@ const copyEntityAndFixTimeFormat = (
|
||||
|
||||
/**
|
||||
* Inplace, no copy again.
|
||||
* @param remote
|
||||
* @param password
|
||||
* @returns
|
||||
*/
|
||||
const decryptRemoteEntityInplace = async (remote: Entity, password: string) => {
|
||||
if (password == undefined || password === "") {
|
||||
const decryptRemoteEntityInplace = async (remote: Entity, cipher: Cipher) => {
|
||||
if (cipher?.isPasswordEmpty()) {
|
||||
remote.key = remote.keyRaw;
|
||||
remote.keyEnc = remote.keyRaw;
|
||||
remote.size = remote.sizeRaw;
|
||||
@ -244,19 +192,9 @@ const decryptRemoteEntityInplace = async (remote: Entity, password: string) => {
|
||||
return remote;
|
||||
}
|
||||
|
||||
if (remote.keyRaw.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE32)) {
|
||||
remote.keyEnc = remote.keyRaw;
|
||||
remote.key = await decryptBase32ToString(remote.keyEnc, password);
|
||||
remote.key = await cipher.decryptName(remote.keyEnc);
|
||||
remote.sizeEnc = remote.sizeRaw;
|
||||
} else if (remote.keyRaw.startsWith(MAGIC_ENCRYPTED_PREFIX_BASE64URL)) {
|
||||
remote.keyEnc = remote.keyRaw;
|
||||
remote.key = await decryptBase64urlToString(remote.keyEnc, password);
|
||||
remote.sizeEnc = remote.sizeRaw;
|
||||
} else {
|
||||
throw Error(
|
||||
`unexpected key to decrypt: ${JSON.stringify(remote, null, 2)}`
|
||||
);
|
||||
}
|
||||
|
||||
// TODO
|
||||
// remote.size = getSizeFromEncToOrig(remote.sizeEnc, password);
|
||||
@ -309,13 +247,10 @@ const ensureMTimeOfRemoteEntityValid = (remote: Entity) => {
|
||||
|
||||
/**
|
||||
* Inplace, no copy again.
|
||||
* @param local
|
||||
* @param password
|
||||
* @returns
|
||||
*/
|
||||
const encryptLocalEntityInplace = async (
|
||||
local: Entity,
|
||||
password: string,
|
||||
cipher: Cipher,
|
||||
remoteKeyEnc: string | undefined
|
||||
) => {
|
||||
// console.debug(
|
||||
@ -333,7 +268,7 @@ const encryptLocalEntityInplace = async (
|
||||
throw Error(`local ${local.keyRaw} is abnormal without key`);
|
||||
}
|
||||
|
||||
if (password === undefined || password === "") {
|
||||
if (cipher.isPasswordEmpty()) {
|
||||
local.sizeEnc = local.sizeRaw; // if no enc, the remote file has the same size
|
||||
local.keyEnc = local.keyRaw;
|
||||
return local;
|
||||
@ -344,7 +279,7 @@ const encryptLocalEntityInplace = async (
|
||||
// it's not filled yet, we fill it
|
||||
// local.size is possibly undefined if it's "prevSync" Entity
|
||||
// but local.key should always have value
|
||||
local.sizeEnc = getSizeFromOrigToEnc(local.size);
|
||||
local.sizeEnc = cipher.getSizeFromOrigToEnc(local.size);
|
||||
}
|
||||
|
||||
if (local.keyEnc === undefined || local.keyEnc === "") {
|
||||
@ -357,10 +292,7 @@ const encryptLocalEntityInplace = async (
|
||||
local.keyEnc = remoteKeyEnc;
|
||||
} else {
|
||||
// we assign a new encrypted key because of no remote
|
||||
// the old version uses base32
|
||||
// local.keyEnc = await encryptStringToBase32(local.key, password);
|
||||
// the new version users base64url
|
||||
local.keyEnc = await encryptStringToBase64url(local.key, password);
|
||||
local.keyEnc = await cipher.encryptName(local.key);
|
||||
}
|
||||
}
|
||||
return local;
|
||||
@ -377,7 +309,7 @@ export const ensembleMixedEnties = async (
|
||||
configDir: string,
|
||||
syncUnderscoreItems: boolean,
|
||||
ignorePaths: string[],
|
||||
password: string,
|
||||
cipher: Cipher,
|
||||
serviceType: SUPPORTED_SERVICES_TYPE
|
||||
): Promise<SyncPlanType> => {
|
||||
const finalMappings: SyncPlanType = {};
|
||||
@ -387,7 +319,7 @@ export const ensembleMixedEnties = async (
|
||||
const remoteCopied = ensureMTimeOfRemoteEntityValid(
|
||||
await decryptRemoteEntityInplace(
|
||||
copyEntityAndFixTimeFormat(remote, serviceType),
|
||||
password
|
||||
cipher
|
||||
)
|
||||
);
|
||||
|
||||
@ -436,14 +368,14 @@ export const ensembleMixedEnties = async (
|
||||
if (finalMappings.hasOwnProperty(key)) {
|
||||
const prevSyncCopied = await encryptLocalEntityInplace(
|
||||
copyEntityAndFixTimeFormat(prevSync, serviceType),
|
||||
password,
|
||||
cipher,
|
||||
finalMappings[key].remote?.keyEnc
|
||||
);
|
||||
finalMappings[key].prevSync = prevSyncCopied;
|
||||
} else {
|
||||
const prevSyncCopied = await encryptLocalEntityInplace(
|
||||
copyEntityAndFixTimeFormat(prevSync, serviceType),
|
||||
password,
|
||||
cipher,
|
||||
undefined
|
||||
);
|
||||
finalMappings[key] = {
|
||||
@ -474,14 +406,14 @@ export const ensembleMixedEnties = async (
|
||||
if (finalMappings.hasOwnProperty(key)) {
|
||||
const localCopied = await encryptLocalEntityInplace(
|
||||
copyEntityAndFixTimeFormat(local, serviceType),
|
||||
password,
|
||||
cipher,
|
||||
finalMappings[key].remote?.keyEnc
|
||||
);
|
||||
finalMappings[key].local = localCopied;
|
||||
} else {
|
||||
const localCopied = await encryptLocalEntityInplace(
|
||||
copyEntityAndFixTimeFormat(local, serviceType),
|
||||
password,
|
||||
cipher,
|
||||
undefined
|
||||
);
|
||||
finalMappings[key] = {
|
||||
@ -1017,7 +949,7 @@ const dispatchOperationToActualV3 = async (
|
||||
db: InternalDBs,
|
||||
vault: Vault,
|
||||
localDeleteFunc: any,
|
||||
password: string
|
||||
cipher: Cipher
|
||||
) => {
|
||||
// console.debug(
|
||||
// `inside dispatchOperationToActualV3, key=${key}, r=${JSON.stringify(
|
||||
@ -1045,7 +977,7 @@ const dispatchOperationToActualV3 = async (
|
||||
if (
|
||||
client.serviceType === "onedrive" &&
|
||||
r.local!.size === 0 &&
|
||||
password === ""
|
||||
cipher.isPasswordEmpty()
|
||||
) {
|
||||
// special treatment for empty files for OneDrive
|
||||
// TODO: it's ugly, any other way?
|
||||
@ -1057,10 +989,10 @@ const dispatchOperationToActualV3 = async (
|
||||
r.key,
|
||||
vault,
|
||||
false,
|
||||
password,
|
||||
cipher,
|
||||
r.local!.keyEnc
|
||||
);
|
||||
await decryptRemoteEntityInplace(entity, password);
|
||||
await decryptRemoteEntityInplace(entity, cipher);
|
||||
await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli);
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
@ -1081,7 +1013,7 @@ const dispatchOperationToActualV3 = async (
|
||||
r.key,
|
||||
vault,
|
||||
r.remote!.mtimeCli!,
|
||||
password,
|
||||
cipher,
|
||||
r.remote!.keyEnc
|
||||
);
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
@ -1092,7 +1024,7 @@ const dispatchOperationToActualV3 = async (
|
||||
);
|
||||
} else if (r.decision === "local_is_deleted_thus_also_delete_remote") {
|
||||
// local is deleted, we need to delete remote now
|
||||
await client.deleteFromRemote(r.key, password, r.remote!.keyEnc);
|
||||
await client.deleteFromRemote(r.key, cipher, r.remote!.keyEnc);
|
||||
await clearPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
@ -1119,11 +1051,11 @@ const dispatchOperationToActualV3 = async (
|
||||
r.key,
|
||||
vault,
|
||||
false,
|
||||
password,
|
||||
cipher,
|
||||
r.local!.keyEnc
|
||||
);
|
||||
// we need to decrypt the key!!!
|
||||
await decryptRemoteEntityInplace(entity, password);
|
||||
await decryptRemoteEntityInplace(entity, cipher);
|
||||
await fullfillMTimeOfRemoteEntityInplace(entity, mtimeCli);
|
||||
await upsertPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
@ -1133,7 +1065,7 @@ const dispatchOperationToActualV3 = async (
|
||||
);
|
||||
} else if (r.decision === "folder_to_be_deleted") {
|
||||
await localDeleteFunc(r.key);
|
||||
await client.deleteFromRemote(r.key, password, r.remote!.keyEnc);
|
||||
await client.deleteFromRemote(r.key, cipher, r.remote!.keyEnc);
|
||||
await clearPrevSyncRecordByVaultAndProfile(
|
||||
db,
|
||||
vaultRandomID,
|
||||
@ -1151,7 +1083,7 @@ export const doActualSync = async (
|
||||
vaultRandomID: string,
|
||||
profileID: string,
|
||||
vault: Vault,
|
||||
password: string,
|
||||
cipher: Cipher,
|
||||
concurrency: number,
|
||||
localDeleteFunc: any,
|
||||
protectModifyPercentage: number,
|
||||
@ -1252,7 +1184,7 @@ export const doActualSync = async (
|
||||
db,
|
||||
vault,
|
||||
localDeleteFunc,
|
||||
password
|
||||
cipher
|
||||
);
|
||||
|
||||
console.debug(`finished ${key}`);
|
||||
|
@ -10,13 +10,13 @@ import {
|
||||
encryptStringToBase64url,
|
||||
getSizeFromEncToOrig,
|
||||
getSizeFromOrigToEnc,
|
||||
} from "../src/encrypt";
|
||||
} from "../src/encryptOpenSSL";
|
||||
import { base64ToBase64url, bufferToArrayBuffer } from "../src/misc";
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe("Encryption tests", () => {
|
||||
describe("Encryption OpenSSL tests", () => {
|
||||
beforeEach(function () {
|
||||
global.window = {
|
||||
crypto: require("crypto").webcrypto,
|
Loading…
Reference in New Issue
Block a user