From 063b2779b568c05a53cb555fbcabfdedd30fda84 Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 17 Oct 2023 22:55:44 +0800 Subject: [PATCH] Improvements and fixes: #803 #892 - Add facilities for using custom CA, client certificate and private key - Use SNI if using custom client certificate and private key - Fix broken `ignoreCertErrors()` - Change behaviour of "Forget Password" button ... - The button becomes disabled after a click event rather than disappearing - The button clears both auth password and TLS private key passphrase - Pressing 'Ctrl + Shift + I' or 'F12' combo launches dev tool --- src/app/app.mjs | 9 +- src/app/libs/libManageSieve/SieveClient.mjs | 40 ++++--- src/app/libs/libManageSieve/SieveSessions.mjs | 74 +++++++++++++ src/app/libs/managesieve.ui/accounts.mjs | 11 +- .../settings/logic/SieveSecurity.mjs | 99 +++++++++++++++++ .../settings/ui/SieveCredentialSettingsUI.mjs | 104 ++++++++++++++++-- .../settings/ui/settings.credentials.html | 32 ++++++ src/app/sieve.mjs | 17 ++- .../libManageSieve/SieveAbstractSession.mjs | 9 ++ .../managesieve.ui/dialogs/SieveDialogUI.mjs | 90 ++++++++++++++- .../dialog.account.tls.passphrase.html | 41 +++++++ src/common/managesieve.ui/i18n/en-US.json | 19 ++++ .../settings/logic/SieveAbstractSecurity.mjs | 21 ++++ 13 files changed, 535 insertions(+), 31 deletions(-) create mode 100644 src/common/managesieve.ui/dialogs/dialog.account.tls.passphrase.html diff --git a/src/app/app.mjs b/src/app/app.mjs index e353cc27e9..73d556760f 100755 --- a/src/app/app.mjs +++ b/src/app/app.mjs @@ -184,7 +184,8 @@ import { SieveI18n } from "./libs/managesieve.ui/utils/SieveI18n.mjs"; return { "general": { security: await account.getSecurity().getTLS(), - sasl: await account.getSecurity().getMechanism() + sasl: await account.getSecurity().getMechanism(), + tlsfiles: await account.getSecurity().getTLSFiles() }, "authentication": { username: await (await account.getAuthentication()).getUsername(), @@ -203,6 +204,7 @@ import { SieveI18n } from "./libs/managesieve.ui/utils/SieveI18n.mjs"; const account = await accounts.getAccountById(msg.payload.account); await (await account.getAuthentication()).forget(); + await (await account.getSecurity()).clearStoredTLSPassphrase(); }, "account-settings-set-credentials": async function (msg) { @@ -213,6 +215,7 @@ import { SieveI18n } from "./libs/managesieve.ui/utils/SieveI18n.mjs"; await account.getSecurity().setTLS(msg.payload.general.security); await account.getSecurity().setMechanism(msg.payload.general.sasl); + await account.getSecurity().setTLSFiles(msg.payload.general.tlsfiles); await account.getAuthentication().setUsername(msg.payload.authentication.username); @@ -629,6 +632,10 @@ import { SieveI18n } from "./libs/managesieve.ui/utils/SieveI18n.mjs"; "decrypt-string" : async(msg) => { return await ipcRenderer.invoke("decrypt-string", msg.payload); + }, + + "ipcrenderer-open-dialog" : async(msg) => { + return await ipcRenderer.invoke("open-dialog", msg.payload.options); } }; diff --git a/src/app/libs/libManageSieve/SieveClient.mjs b/src/app/libs/libManageSieve/SieveClient.mjs index 958ac3f230..68363dbb6a 100644 --- a/src/app/libs/libManageSieve/SieveClient.mjs +++ b/src/app/libs/libManageSieve/SieveClient.mjs @@ -140,10 +140,13 @@ class SieveNodeClient extends SieveAbstractClient { return await new Promise((resolve, reject) => { // Upgrade the current socket. - // this.tlsSocket = tls.TLSSocket(socket, options).connect(); this.tlsSocket = tls.connect({ socket: this.socket, - rejectUnauthorized: false + rejectUnauthorized: false, + // Do SNI if using client cert because users who use it will probably + // want it ;) + servername: options.tlsContext ? this.host : undefined, + secureContext: options.tlsContext }); this.tlsSocket.on('secureConnect', () => { @@ -187,25 +190,29 @@ class SieveNodeClient extends SieveAbstractClient { // // It will be non null e.g. for self signed certificates. const error = this.tlsSocket.ssl.verifyError(); + let code = null; - if ((error !== null ) && (options.ignoreCertErrors.includes(error.code))) { + if (error !== null ) { + code = error.code; - if (this.isPinnedCert(cert, options.fingerprints)) { - this.secured = true; - resolve(); - return; - } + if (options.ignoreCertErrors.includes(error.code)) { + if (this.isPinnedCert(cert, options.fingerprints)) { + this.secured = true; + resolve(); + return; + } - throw new SieveCertValidationException({ - host: this.host, - port: this.port, + throw new SieveCertValidationException({ + host: this.host, + port: this.port, - fingerprint: cert.fingerprint, - fingerprint256: cert.fingerprint256, + fingerprint: cert.fingerprint, + fingerprint256: cert.fingerprint256, - code: error.code, - message: error.message - }); + code: code, + message: error.message + }); + } } if (this.tlsSocket.authorizationError === "ERR_TLS_CERT_ALTNAME_INVALID") { @@ -223,6 +230,7 @@ class SieveNodeClient extends SieveAbstractClient { fingerprint: cert.fingerprint, fingerprint256: cert.fingerprint256, + code: code, message: `Error upgrading (${this.tlsSocket.authorizationError})` }); diff --git a/src/app/libs/libManageSieve/SieveSessions.mjs b/src/app/libs/libManageSieve/SieveSessions.mjs index 2a50147973..e4be01f60e 100644 --- a/src/app/libs/libManageSieve/SieveSessions.mjs +++ b/src/app/libs/libManageSieve/SieveSessions.mjs @@ -9,6 +9,8 @@ * Thomas Schmid */ +const fs = require('fs'); +const tls = require('tls'); import { SieveSession } from "./SieveSession.mjs"; /** @@ -89,6 +91,77 @@ class SieveNodeSessions { return await (await account.getAuthorization()).getAuthorization(); } + /** + * Called before STARTTLS is initiated. + * + * @param {SieveAccount}account the SieveAccount instance + * @returns {tls.SecurityContext} + * the SecurityContext instance set up for the particular tls connection + */ + async onStartTLS(account) { + const options = {}; + let loaded = false; + const sec = await account.getSecurity(); + const tlsfiles = await sec.getTLSFiles(); + let r; + let tlsCtx = undefined; + + if (tlsfiles.cachain) { + options.ca = await fs.promises.readFile(tlsfiles.cachain); + loaded = true; + } + if (tlsfiles.cert) { + options.cert = await fs.promises.readFile(tlsfiles.cert); + loaded = true; + } + if (tlsfiles.key) { + options.key = await fs.promises.readFile(tlsfiles.key); + loaded = true; + } + + if (!loaded) { + // No option set. The user doesn't want this. + return undefined; + } + + options.passphrase = await sec.getStoredTLSPassphrase(); + + do { + try { + tlsCtx = tls.createSecureContext(options); + break; + } + catch (ex) { + // Assume that the error is caused by wrong passphrase. + // Nodejs does not gift-wrap errors from the underlying crypto + // lib(openssl). Making guesses on what the error is based on the + // error messages from the crypto library could be a bad idea. + + // Even if createSecureContext() fails because of some other reason, + // the user may notice it from the error message that would say + // something other than "BAD_PASS" on the next iteration. + r = await sec.promptPassphrase(tlsfiles.key, ex.toString()); + + if (r) { + // New passphrase to try on next iteration + options.passphrase = r.passphrase; + continue; + } + else { + // The user closed the dialog. Give up. + throw ex; + } + } + } while (!tlsCtx); + + if (r && r.remember) { + // This means a new passphrase has been tried successfully and the + // user intends to save it. + await sec.setStoredTLSPassphrase(r.passphrase); + } + + return tlsCtx; + } /** @@ -123,6 +196,7 @@ class SieveNodeSessions { session.on("authenticate", async (hasPassword) => { return await this.onAuthenticate(account, hasPassword); }); session.on("authorize", async () => { return await this.onAuthorize(account); }); + session.on("starttls", async () => { return await this.onStartTLS(account); }); this.sessions.set(id, session); } diff --git a/src/app/libs/managesieve.ui/accounts.mjs b/src/app/libs/managesieve.ui/accounts.mjs index b0839fa989..5dec743598 100644 --- a/src/app/libs/managesieve.ui/accounts.mjs +++ b/src/app/libs/managesieve.ui/accounts.mjs @@ -24,7 +24,8 @@ import { SieveDeleteAccountDialog, SieveErrorDialog, SievePasswordDialog, - SieveAuthorizationDialog + SieveAuthorizationDialog, + SieveTLSPassphraseDialog } from "./dialogs/SieveDialogUI.mjs"; /** @@ -176,8 +177,16 @@ async function main() { SieveIpcClient.setRequestHandler("accounts", "account-disconnected", async (msg) => { return await accounts.render(msg.payload); }); + SieveIpcClient.setRequestHandler("accounts", "tls-show-passphrase", + async (msg) => { + return await (new SieveTLSPassphraseDialog( + msg.payload.filepath, + msg.payload.options)).show(); + }); + accounts.render(); (new SieveUpdaterUI()).check(); + } if (document.readyState !== 'loading') diff --git a/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs b/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs index 5cad68257b..b6ab280afc 100644 --- a/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs +++ b/src/app/libs/managesieve.ui/settings/logic/SieveSecurity.mjs @@ -9,6 +9,7 @@ * Thomas Schmid */ +import { SieveIpcClient } from "../../utils/SieveIpcClient.mjs"; import { SieveAbstractSecurity, SECURITY_NONE, @@ -18,6 +19,10 @@ import { const PREF_MECHANISM = "security.mechanism"; const PREF_TLS = "security.tls"; +const PREF_TLSFILES_CA = "security.tlsfiles.ca"; +const PREF_TLSFILES_CERT = "security.tlsfiles.cert"; +const PREF_TLSFILES_KEY = "security.tlsfiles.key"; +const PREF_TLSFILES_PASSPHRASE = "security.tlsfiles.passphrase"; /** * Manages the account's security related settings @@ -71,6 +76,100 @@ class SieveSecurity extends SieveAbstractSecurity { return this; } + /** + * Gets the TLS files map object. + * + * @returns {object} + * the TLS files map object + */ + async getTLSFiles () { + const cfg = this.account.getConfig(); + + return await { + cachain: await cfg.getString(PREF_TLSFILES_CA, ""), + cert: await cfg.getString(PREF_TLSFILES_CERT, ""), + key: await cfg.getString(PREF_TLSFILES_KEY, "") + }; + } + + /** + * Sets the TLS files map object. + * + * @param {object} map + * the map object + * @returns {SieveSecurity} + * a self reference + */ + async setTLSFiles (map) { + const cfg = this.account.getConfig(); + + await cfg.setString(PREF_TLSFILES_CA, map.cachain); + await cfg.setString(PREF_TLSFILES_CERT, map.cert); + await cfg.setString(PREF_TLSFILES_KEY, map.key); + + return this; + } + + /** + * @inheritdoc + */ + async getStoredTLSPassphrase() { + const cfg = this.account.getConfig(); + const enp = await cfg.getValue(PREF_TLSFILES_PASSPHRASE); + + if (!enp) { + return null; + } + + return await SieveIpcClient.sendMessage( + "core", "decrypt-string", enp, window); + } + + /** + * Encrypts and stores the passphrase for the private key. + * + * @param {string} passphrase the passphrase string + * @returns {SieveSecurity} a self reference + */ + async setStoredTLSPassphrase(passphrase) { + const cfg = this.account.getConfig(); + const enp = await SieveIpcClient.sendMessage( + "core", "encrypt-string", passphrase, window); + + await cfg.setValue(PREF_TLSFILES_PASSPHRASE, enp); + + return this; + } + + /** + * Clears the stored TLS passphrase + * + * @returns {SieveSecurity} a self reference + */ + async clearStoredTLSPassphrase() { + await this.account.getConfig().removeKey(PREF_TLSFILES_PASSPHRASE); + return this; + } + + /** + * Prompt a new passphrase for the private key. + * + * @param {string} filepath the path to the private key + * @param {string} error the message describing the error, usually from openssl + * @returns {{ + * passphrase: {string}, + * remember: {boolean} + * }} the object containing prompt result + */ + async promptPassphrase(filepath, error) { + return await SieveIpcClient.sendMessage( + "accounts", + "tls-show-passphrase", + { + filepath: filepath, + options: { remember: true, error: error } + }); + } } export { SieveSecurity }; diff --git a/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs b/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs index 37e2244ca1..5c57466110 100644 --- a/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs +++ b/src/app/libs/managesieve.ui/settings/ui/SieveCredentialSettingsUI.mjs @@ -12,6 +12,7 @@ /* global bootstrap */ import { SieveTemplate } from "./../../utils/SieveTemplate.mjs"; +import { SieveI18n } from "./../../utils/SieveI18n.mjs"; const SECURITY_NONE = 0; const SECURITY_EXPLICIT = 1; @@ -216,6 +217,40 @@ class SieveCredentialsSettingsUI { return this; } + /** + * Sets the file paths to the CA bundle, certificate and private key. + * + * @param {object} tlsfiles + * a map object containing tls file paths + * @returns {SieveServerSettingsUI} + * a self reference + */ + setConnectionCertificates(tlsfiles) { + const parent = this.getDialog(); + + parent.querySelector(".sieve-settings-cachain").value = tlsfiles.cachain; + parent.querySelector(".sieve-settings-cert").value = tlsfiles.cert; + parent.querySelector(".sieve-settings-key").value = tlsfiles.key; + + return this; + } + + /** + * Gets the current tls files settings. + * + * @returns {object} + * an object containing current settings of paths to tls files for connection + */ + getConnectionCertificates() { + const parent = this.getDialog(); + + return { + cachain: parent.querySelector(".sieve-settings-cachain").value, + cert: parent.querySelector(".sieve-settings-cert").value, + key: parent.querySelector(".sieve-settings-key").value + }; + } + /** * Shows the advanced setting */ @@ -264,6 +299,61 @@ class SieveCredentialsSettingsUI { modal.hide(); }); + const getCertFilters = (i18n) => { + return [ + { + name: i18n.getString("connection.browsefile.filtername.cert"), + extensions: ['crt', 'der', 'pem'] + }, + { name: i18n.getString("filedialog.filter.allfiles"), extensions: ['*'] } + ]; + }; + const getKeyFilters = (i18n) => { + return [ + { + name: i18n.getString("connection.browsefile.filtername.key"), + extensions: ['key', 'pem'] + }, + { name: i18n.getString("filedialog.filter.allfiles"), extensions: ['*'] } + ]; + }; + const installTLSBrowseBtnEvent = (btn, input, filtersFunc, titleI18n) => { + dialog + .querySelector(btn) + .addEventListener("click", async () => { + const i18n = SieveI18n.getInstance(); + const inputtag = dialog.querySelector(input); + + const r = await this.account.send("ipcrenderer-open-dialog", { + options: { + title: i18n.getString(titleI18n), + defaultPath: inputtag.value ? inputtag.value : "", + filters: filtersFunc(i18n) + } + }); + + if (!r.canceled) { + inputtag.value = r.filePaths[0]; + } + }); + }; + + installTLSBrowseBtnEvent( + ".sieve-settings-browse-cachain", + ".sieve-settings-cachain", + getCertFilters, + "connection.browsefile.title.ca"); + installTLSBrowseBtnEvent( + ".sieve-settings-browse-cert", + ".sieve-settings-cert", + getCertFilters, + "connection.browsefile.title.cert"); + installTLSBrowseBtnEvent( + ".sieve-settings-browse-key", + ".sieve-settings-key", + getKeyFilters, + "connection.browsefile.title.key"); + return await new Promise((resolve) => { dialog.addEventListener('hidden.bs.modal', () => { @@ -285,7 +375,8 @@ class SieveCredentialsSettingsUI { const settings = { general: { security: this.getConnectionSecurity(), - sasl: this.getSaslMechanism() + sasl: this.getSaslMechanism(), + tlsfiles: this.getConnectionCertificates() }, authentication: { username: this.getAuthentication(), @@ -332,25 +423,22 @@ class SieveCredentialsSettingsUI { }); }); - // Show the forget password button only when a password is stored. - if (credentials.authentication.stored) - parent.querySelector(".sieve-settings-forget-password").classList.remove("d-none"); - else - parent.querySelector(".sieve-settings-forget-password").classList.add("d-none"); - parent .querySelector(".sieve-settings-forget-password button") .addEventListener("click", async () => { await this.account.send("account-settings-forget-credentials"); this.getDialog() - .querySelector(".sieve-settings-forget-password").classList.add("d-none"); + .querySelector(".sieve-settings-forget-password button").disabled = true; }); // Authorization settings.... this.setAuthorizationType(credentials.authorization.type); this.setAuthorization(credentials.authorization.username); + // Connection TLS files + this.setConnectionCertificates(credentials.general.tlsfiles); + parent .querySelectorAll(".sieve-settings-authorization .dropdown-item") .forEach((item) => { diff --git a/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html b/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html index 13c236cb22..72716cc59e 100644 --- a/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html +++ b/src/app/libs/managesieve.ui/settings/ui/settings.credentials.html @@ -128,6 +128,38 @@ + +

+ +

+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+ diff --git a/src/app/sieve.mjs b/src/app/sieve.mjs index 611e7a274a..43a81850da 100644 --- a/src/app/sieve.mjs +++ b/src/app/sieve.mjs @@ -85,6 +85,15 @@ async function createWindow() { } }); + // Listen in for 'Ctrl + Shift + I' and 'F12' to show dev tools + win.webContents.on('before-input-event', (e, input) => { + if ((input.control && input.shift && input.key.toLowerCase() === 'i') || + input.key.toLowerCase() === 'f12') + { + win.webContents.openDevTools({ "mode": "detach", "activate": true }); + } + }); + const handleRedirect = (e, uri) => { if (uri !== win.webContents.getURL()) { e.preventDefault(); @@ -122,15 +131,15 @@ async function main() { win.focus(); }); - ipcMain.handle("open-dialog", async(event, options) => { + ipcMain.handle("open-dialog", async (event, options) => { return await dialog.showOpenDialog(options); }); - ipcMain.handle("save-dialog", async(event, options) => { + ipcMain.handle("save-dialog", async (event, options) => { return await dialog.showSaveDialog(options); }); - ipcMain.handle("get-version", async() => { + ipcMain.handle("get-version", async () => { return await app.getVersion(); }); @@ -138,7 +147,7 @@ async function main() { // Open the DevTools. win.webContents.openDevTools({ "mode": "detach", - "activate" : true + "activate": true }); }); diff --git a/src/common/libManageSieve/SieveAbstractSession.mjs b/src/common/libManageSieve/SieveAbstractSession.mjs index f2f7dbd9b9..9a414c74e5 100644 --- a/src/common/libManageSieve/SieveAbstractSession.mjs +++ b/src/common/libManageSieve/SieveAbstractSession.mjs @@ -310,6 +310,11 @@ class SieveAbstractSession { return this; } + if (name === "starttls") { + this.listeners.onStartTLS = callback; + return this; + } + throw new SieveClientException(`Unknown callback handler ${name}`); } @@ -418,6 +423,10 @@ class SieveAbstractSession { if (!this.getCompatibility().canStartTLS()) throw new SieveClientException("Server does not support a secure connection."); + if (this.listeners.onStartTLS) { + options.tlsContext = await this.listeners.onStartTLS(); + } + this.getLogger().logSession(`... requesting starttls ...`); await this.sendRequest(new SieveStartTLSRequest()); diff --git a/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs b/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs index 8a8757c4fa..94b333ee06 100644 --- a/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs +++ b/src/common/managesieve.ui/dialogs/SieveDialogUI.mjs @@ -678,6 +678,93 @@ class SieveErrorDialog extends SieveDialog { } } +/** + * Prompts the passphrase used to decrypt the private key. + * The show method returns null or the object. + */ +class SieveTLSPassphraseDialog extends SieveDialog { + + /** + * Creates a password dialog. + * + * @param {string} filepath + * the path to the private key in question. + * @param {{ + * remember : boolean, + * error : string + * }} [options] + * extended additional options. + * In case "remember" is set to true a switch will be rendered which allows + * the user to select if the passphrase should be stored. + * "error" can be set to display the error occurred in the underlying + * crypto lib. + */ + constructor(filepath, options) { + super(); + this.filepath = filepath; + + if (typeof (options) === "undefined" || options === null) + options = {}; + + this.options = options; + } + + /** + * @inheritdoc + */ + getTemplate() { + return "./dialogs/dialog.account.tls.passphrase.html"; + } + + /** + * @inheritdoc + */ + onInit() { + const dialog = this.getDialog(); + + if (!this.options.remember) + dialog.querySelector(".sieve-passphrase-remember").style.display = "none"; + + dialog.querySelector(".sieve-privatekey-path").textContent = this.filepath; + + if (this.options.error) { + dialog.querySelector(".sieve-privatekey-error").textContent = this.options.error; + } + + this.getDialog().querySelector('.sieve-passphrase').addEventListener("keypress", (e) => { + if (e.key === "Enter") { + const event = document.createEvent('HTMLEvents'); + event.initEvent('click', true, false); + this.getDialog().querySelector(".sieve-dialog-resolve").dispatchEvent(event); + } + }); + } + + /** + * @inheritdoc + */ + onShown() { + this.getDialog().querySelector('.sieve-passphrase').focus(); + } + + /** + * @inheritdoc + */ + onAccept() { + return { + "passphrase": this.getDialog().querySelector(".sieve-passphrase").value, + "remember": document.querySelector("#sieve-passphrase-remember").checked + }; + } + + /** + * @inheritdoc + */ + onCancel() { + return null; + } +} + export { SievePasswordDialog, SieveRenameScriptDialog, @@ -688,5 +775,6 @@ export { SieveAuthorizationDialog, SieveScriptBusyDialog, SieveScriptSaveDialog, - SieveErrorDialog + SieveErrorDialog, + SieveTLSPassphraseDialog }; diff --git a/src/common/managesieve.ui/dialogs/dialog.account.tls.passphrase.html b/src/common/managesieve.ui/dialogs/dialog.account.tls.passphrase.html new file mode 100644 index 0000000000..96dca72209 --- /dev/null +++ b/src/common/managesieve.ui/dialogs/dialog.account.tls.passphrase.html @@ -0,0 +1,41 @@ + + diff --git a/src/common/managesieve.ui/i18n/en-US.json b/src/common/managesieve.ui/i18n/en-US.json index aae875d0b5..f8589ed0d4 100644 --- a/src/common/managesieve.ui/i18n/en-US.json +++ b/src/common/managesieve.ui/i18n/en-US.json @@ -15,6 +15,8 @@ "settings.less" : "Less", "settings.accept" : "Apply", + "filedialog.filter.allfiles": "All Files", + "account.disconnected.title" : "Not Connected to the Server", "account.disconnected.description" : "The account is not connected to the server.", "account.connect" : "Connect", @@ -77,6 +79,16 @@ "connection.handshake.description" : "The TLS handshake specified in the Manage Sieve RFC is defined as explicit. An explicit handshake is theoretically weaker than an implicit, but no vulnerabilities are known for Manage Sieve. Also no server implementation officially supports an implicit handshake, but most of them can be reconfigured to do so. Keep in mind both the client and the server have to use the same handshake type.", "connection.handshake.explicit" : "Explicit (Default)", "connection.handshake.implicit" : "Implicit", + "connection.certificate.description": "Set the TLS client certificate and private key to present to the server. Set the CA Bundle to do server verification.", + "connection.cachain": "CA Bundle", + "connection.cert": "Certificate", + "connection.key": "Private Key", + "connection.browsefile": "Browse", + "connection.browsefile.title.ca": "Select TLS CA file", + "connection.browsefile.title.cert": "Select TLS certificate file", + "connection.browsefile.title.key": "Select TLS private key file", + "connection.browsefile.filtername.cert": "Certificate (crt, pem, der)", + "connection.browsefile.filtername.key": "Private Key (key, pem, der)", "credentials.title" : "Credentials", "credentials.security" : "Security", @@ -141,6 +153,13 @@ "password.dialog.remember" : "Remember password", "password.dialog.accept" : "Login", + "tlspassdialog.title": "Encrypted Private Key", + "tlspassdialog.description": "The private key is encrypted with a passphrase. Please enter the passphrase for the private key.", + "tlspassdialog.privatekey": "Private Key", + "tlspassdialog.passphrase": "Passphrase", + "tlspassdialog.remember": "Remember passphrase", + "tlspassdialog.accept": "Continue", + "authorization.title" : "Authorization Required", "authorization.description" : "Please enter the user as which you would like to be authorized.", "authorization.account" : "Account", diff --git a/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs b/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs index dffe4e5389..92811a26fe 100644 --- a/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs +++ b/src/common/managesieve.ui/settings/logic/SieveAbstractSecurity.mjs @@ -52,6 +52,27 @@ class SieveAbstractSecurity { async getMechanism() { return await "default"; } + + /** + * Gets the TLS files map object. + * + * @returns {object} + * the TLS files map object + */ + async getTLSFiles () { + return await { cachain: "", cert: "", key: "" }; + } + + /** + * Gets the stored passphrase for the private key. + * + * @returns {(string|null)} + * stored passphrase if exists. + * null if no passphrase is stored. + */ + async getStoredTLSPassphrase() { + return await null; + } } export {