From 1d4587f7ea145705ce4bf1c330a1fcc14536ff7b Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Tue, 3 Nov 2026 11:31:14 +0100 Subject: [PATCH] fix: stop a stuck certificate call holding mail rendering, and let a server's certificate access be taken back --- .../desktop-keystore-settings.test.tsx | 52 ++++++++++++++++++ .../settings/desktop-keystore-settings.tsx | 42 +++++++++++++++ components/settings/general-settings.tsx | 2 + lib/__tests__/plugin-keystore-runtime.test.ts | 14 +++-- lib/__tests__/plugin-keystore.test.ts | 53 +++++++++++++++++++ lib/desktop/types.ts | 16 ++++-- lib/plugin-hooks.ts | 17 ++++++ lib/plugin-sandbox/host-api-keystore.ts | 12 +++-- lib/plugin-sandbox/runtime.tsx | 13 ++--- locales/ar/common.json | 10 +++- locales/ca/common.json | 10 +++- locales/cs/common.json | 10 +++- locales/da/common.json | 10 +++- locales/de/common.json | 10 +++- locales/en/common.json | 10 +++- locales/es/common.json | 10 +++- locales/et/common.json | 10 +++- locales/fa/common.json | 10 +++- locales/fr/common.json | 10 +++- locales/he/common.json | 10 +++- locales/hu/common.json | 10 +++- locales/it/common.json | 10 +++- locales/ja/common.json | 10 +++- locales/ko/common.json | 10 +++- locales/lv/common.json | 10 +++- locales/mn/common.json | 10 +++- locales/nb/common.json | 10 +++- locales/nl/common.json | 10 +++- locales/pl/common.json | 10 +++- locales/pt/common.json | 10 +++- locales/ro/common.json | 10 +++- locales/ru/common.json | 10 +++- locales/sk/common.json | 10 +++- locales/th/common.json | 10 +++- locales/tr/common.json | 10 +++- locales/uk/common.json | 10 +++- locales/vi/common.json | 10 +++- locales/zh-TW/common.json | 10 +++- locales/zh/common.json | 10 +++- 39 files changed, 474 insertions(+), 47 deletions(-) create mode 100644 components/settings/__tests__/desktop-keystore-settings.test.tsx create mode 100644 components/settings/desktop-keystore-settings.tsx diff --git a/components/settings/__tests__/desktop-keystore-settings.test.tsx b/components/settings/__tests__/desktop-keystore-settings.test.tsx new file mode 100644 index 000000000..3a48141cb --- /dev/null +++ b/components/settings/__tests__/desktop-keystore-settings.test.tsx @@ -0,0 +1,52 @@ +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { DesktopKeystoreSettings } from '../desktop-keystore-settings'; + +// Taking back a server's use of the certificates on this computer. The stored yes lives +// in the shell; this row is the way out, and only shows when there is something to take back. + +const toast = vi.hoisted(() => ({ success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn() })); +vi.mock('@/stores/toast-store', () => ({ toast })); +vi.mock('../settings-section', async (importOriginal) => ({ + ...(await importOriginal()), + SettingsSection: ({ children }: { children: React.ReactNode }) =>
{children}
, +})); + +function bridge(state: string) { + const revokeAccess = vi.fn(async () => {}); + (window as unknown as { bulwarkDesktop?: unknown }).bulwarkDesktop = { + version: 1, + platform: 'win32', + keystore: { accessState: vi.fn(async () => state), revokeAccess }, + }; + return revokeAccess; +} + +afterEach(() => { + delete (window as unknown as { bulwarkDesktop?: unknown }).bulwarkDesktop; + toast.success.mockClear(); +}); + +describe('the keystore access row', () => { + it('takes back a stored yes through the shell, and goes away', async () => { + const revoke = bridge('stored'); + render(); + fireEvent.click(await screen.findByRole('button', { name: 'revoke' })); + await waitFor(() => expect(revoke).toHaveBeenCalledTimes(1)); + await waitFor(() => expect(screen.queryByRole('button', { name: 'revoke' })).toBeNull()); + expect(toast.success).toHaveBeenCalledWith('revoked'); + }); + + it.each(['off', 'policy', 'app'])('shows nothing when the state is %s', async (state) => { + bridge(state); + const { container } = render(); + await new Promise((resolve) => setTimeout(resolve, 5)); + expect(container.textContent).toBe(''); + }); + + it('shows nothing in a browser', async () => { + const { container } = render(); + await new Promise((resolve) => setTimeout(resolve, 5)); + expect(container.textContent).toBe(''); + }); +}); diff --git a/components/settings/desktop-keystore-settings.tsx b/components/settings/desktop-keystore-settings.tsx new file mode 100644 index 000000000..381f502ab --- /dev/null +++ b/components/settings/desktop-keystore-settings.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useTranslations } from "next-intl"; +import { Button } from "@/components/ui/button"; +import { toast } from "@/stores/toast-store"; +import { SettingsSection, SettingItem } from "./settings-section"; + +/** + * In the desktop app on an instance: the user's yes to this server using certificates on + * this computer, stored in the shell, and the way to take it back. Nothing shows in a + * browser, in standalone, or when an organisation's policy set it. + */ +export function DesktopKeystoreSettings() { + const t = useTranslations("desktop_keystore"); + const [stored, setStored] = useState(false); + + useEffect(() => { + let live = true; + window.bulwarkDesktop?.keystore?.accessState?.().then( + (state) => { if (live) setStored(state === "stored"); }, + () => {}, + ); + return () => { live = false; }; + }, []); + + if (!stored) return null; + + const revoke = async () => { + await window.bulwarkDesktop?.keystore?.revokeAccess(); + setStored(false); + toast.success(t("revoked")); + }; + + return ( + + + + + + ); +} diff --git a/components/settings/general-settings.tsx b/components/settings/general-settings.tsx index 41d2ba072..0a03272bf 100644 --- a/components/settings/general-settings.tsx +++ b/components/settings/general-settings.tsx @@ -4,6 +4,7 @@ import { useTranslations } from 'next-intl'; import { useSettingsStore } from '@/stores/settings-store'; import { SettingsSection, SettingItem, ToggleSwitch } from './settings-section'; import { LanguageSettings } from './language-settings'; +import { DesktopKeystoreSettings } from './desktop-keystore-settings'; import { ProtocolHandlerSettings } from './protocol-handler-settings'; import { useTour } from '@/components/tour/tour-provider'; import { Button } from '@/components/ui/button'; @@ -25,6 +26,7 @@ export function GeneralSettings({ supportsCalendar }: { supportsCalendar: boolea {!IS_LITE && } + diff --git a/lib/__tests__/plugin-keystore-runtime.test.ts b/lib/__tests__/plugin-keystore-runtime.test.ts index d50e1b172..ab1d59dec 100644 --- a/lib/__tests__/plugin-keystore-runtime.test.ts +++ b/lib/__tests__/plugin-keystore-runtime.test.ts @@ -71,21 +71,29 @@ describe('api.keystore in the sandbox', () => { expect(g.__pluginApi!.host.features.keystore).toBeFalsy(); }, 30_000); - it('no call gives up while the host has not answered: a PIN can take minutes', async () => { + it('a list has the ordinary RPC timeout: it never waits on a card', async () => { + await boot({ keystore: true }); + vi.useFakeTimers(); + const listing = g.__pluginApi!.keystore.list(); + const outcome = listing.then(() => 'answered', (error: Error) => error.message); + await vi.advanceTimersByTimeAsync(31_000); + await expect(outcome).resolves.toMatch(/timed out/); + }, 30_000); + + it('no key operation gives up while the host has not answered: a PIN can take minutes', async () => { await boot({ keystore: true }); vi.useFakeTimers(); const api = g.__pluginApi!.keystore; const digest = new Uint8Array(32); const settled: string[] = []; const watch = (name: string, promise: Promise) => promise.then(() => settled.push(name), () => settled.push(`${name} rejected`)); - watch('list', api.list()); watch('sign', api.sign('id', digest, { scheme: 'rsa-pkcs1', hash: 'SHA-256' })); watch('decrypt', api.decrypt('id', new Uint8Array(256), { scheme: 'rsa-pkcs1' })); watch('agree', api.agree('id', new Uint8Array(91))); // Past the RPC's 30 s and past the network calls' 120 s. await vi.advanceTimersByTimeAsync(600_000); expect(settled).toEqual([]); - expect(requests().map((request) => request.method)).toEqual(['keystore.list', 'keystore.sign', 'keystore.decrypt', 'keystore.agree']); + expect(requests().map((request) => request.method)).toEqual(['keystore.sign', 'keystore.decrypt', 'keystore.agree']); }, 30_000); it('passes the bytes as bytes, and hands an error back with the code as its message', async () => { diff --git a/lib/__tests__/plugin-keystore.test.ts b/lib/__tests__/plugin-keystore.test.ts index e8109181b..3a1426d92 100644 --- a/lib/__tests__/plugin-keystore.test.ts +++ b/lib/__tests__/plugin-keystore.test.ts @@ -281,6 +281,59 @@ describe('a keystore call and the hook timer', () => { expect(hasDialogFor(ID)).toBe(false); }); + it('a list takes no hold: it never waits on a card, and a hung list must not stop the hook timer', async () => { + const bridge = fakeBridge(); + bridge.list.mockImplementation(() => new Promise(() => {})); + installBridge(bridge); + renderHooks.onRenderEmailBody.register(ID, async (body: object) => { + await dispatchApiCall(plugin(), 'keystore.list', [], { privileged: true }); + return { ...body, html: 'never' }; + }); + const ran = renderHooks.onRenderEmailBody.transform({ html: 'as it was' }); + expect(hasDialogFor(ID)).toBe(false); + await vi.advanceTimersByTimeAsync(6000); + await expect(ran).resolves.toEqual({ html: 'as it was' }); + expect(console.error).toHaveBeenCalled(); + }); + + it('a call that never answers cannot hold the hook for ever: there is an absolute deadline', async () => { + const bridge = fakeBridge(); + bridge.decrypt.mockImplementation(() => new Promise(() => {})); + installBridge(bridge); + // This call never ends, so its hold never does: an id of its own. + const hung = plugin({ id: 'hung-plugin' }); + renderHooks.onRenderEmailBody.register(hung.id, async (body: object) => { + await dispatchApiCall(hung, 'keystore.decrypt', ['id', new Uint8Array(256), { scheme: 'rsa-pkcs1' }], { privileged: true }); + return { ...body, html: 'never' }; + }); + const ran = renderHooks.onRenderEmailBody.transform({ html: 'as it was' }); + let settled = false; + void ran.then(() => { settled = true; }); + // Longer than the shell's own 5 minute cap, which would normally end it first. + await vi.advanceTimersByTimeAsync(5 * 60_000); + expect(settled).toBe(false); + await vi.advanceTimersByTimeAsync(11 * 60_000); + expect(settled).toBe(true); + await expect(ran).resolves.toEqual({ html: 'as it was' }); + expect(console.error).toHaveBeenCalled(); + }); + + it('holds that keep coming and going do not reset the deadline', async () => { + renderHooks.onRenderEmailBody.register(ID, () => new Promise(() => {})); + let release = holdForUser(ID); + const ran = renderHooks.onRenderEmailBody.transform({ html: 'as it was' }); + let settled = false; + void ran.then(() => { settled = true; }); + for (let minute = 0; minute < 20 && !settled; minute += 1) { + await vi.advanceTimersByTimeAsync(4000); + release(); + release = holdForUser(ID); + await vi.advanceTimersByTimeAsync(60_000 - 4000); + } + release(); + expect(settled).toBe(true); + }); + it('a hook that hangs with no keystore call still times out', async () => { renderHooks.onRenderEmailBody.register(ID, () => new Promise(() => {})); const ran = renderHooks.onRenderEmailBody.transform({ html: 'as it was' }); diff --git a/lib/desktop/types.ts b/lib/desktop/types.ts index 4f6502313..5c60da8bf 100644 --- a/lib/desktop/types.ts +++ b/lib/desktop/types.ts @@ -141,8 +141,13 @@ export interface DesktopKeystoreCertificate { signSchemes: string[]; decryptSchemes: string[]; canAgree: boolean; + /** The key can also sign the user in somewhere: the user is asked per use. */ + askEachUse: boolean; } +/** Whether this origin may use the keystore at all. `app` is standalone. */ +export type DesktopKeystoreAccessState = 'app' | 'policy' | 'stored' | 'off'; + export type DesktopKeystoreSignScheme = | { scheme: 'rsa-pkcs1'; hash: DesktopKeystoreHash } /** MGF1 on the same hash, salt as long as the hash. */ @@ -158,11 +163,13 @@ export type DesktopKeystoreDecryptScheme = /** * The OS certificate store and smart cards. Present only where the native helper * loaded and the OS has one. Failures reject with a bare code as the message: - * keystore:cancelled, not-found, unsupported, locked, invalid, failed. The key never - * leaves the OS, and its own prompts stay on, so a call may take as long as a PIN. + * keystore:cancelled, not-found, unsupported, locked, invalid, failed. The shell asks the + * user in a native dialog before any of this, on top of which the OS has its own prompts, + * so a call may take a while. On an instance it stays off until policy or the user allows + * it. The key never leaves the OS. */ export interface DesktopKeystoreBridge { - /** Certificates fit for mail that have a private key. Never prompts. */ + /** Certificates fit for mail that have a private key. The OS doesn't prompt. */ list: () => Promise; /** RSA gives the raw signature, ECDSA a DER ECDSA-Sig-Value. */ sign: (id: string, digest: Uint8Array, scheme: DesktopKeystoreSignScheme) => Promise; @@ -170,6 +177,9 @@ export interface DesktopKeystoreBridge { decrypt: (id: string, data: Uint8Array, scheme: DesktopKeystoreDecryptScheme) => Promise; /** The raw shared secret, big-endian. The peer's key is an EC SubjectPublicKeyInfo. */ agree: (id: string, peerPublicKey: Uint8Array) => Promise; + accessState: () => Promise; + /** Takes back a stored yes and this session's grants. Only ever narrows. */ + revokeAccess: () => Promise; } export interface BulwarkDesktopBridge { diff --git a/lib/plugin-hooks.ts b/lib/plugin-hooks.ts index 655ff4695..6686e7e37 100644 --- a/lib/plugin-hooks.ts +++ b/lib/plugin-hooks.ts @@ -71,6 +71,13 @@ const INTERCEPT_TIMEOUT_MS = 60_000; // counted them toward auto-disable. const BLOB_UPLOAD_TIMEOUT_MS = 15 * 60_000; +/** + * The longest a hook may stay waiting on a dialog or an OS prompt, from its start. A + * person answers well within this. A plugin whose dialog is never answered, or a call the + * OS never finishes, must not hold mail rendering or a send forever. + */ +const HELD_HOOK_DEADLINE_MS = 10 * 60_000; + class HookTimeoutError extends Error {} /** Tells the hook timer when a plugin is waiting on the user. */ @@ -97,20 +104,30 @@ function withTimeout(pluginId: string, promise: T | Promise, ms: number = if (!(promise instanceof Promise)) return Promise.resolve(promise); return new Promise((resolve, reject) => { let timer: ReturnType | undefined; + let deadline: ReturnType | undefined; let stopWaiting: (() => void) | undefined; + const started = Date.now(); const arm = () => { stopWaiting = undefined; timer = setTimeout(expire, ms); }; const expire = () => { if (dialogGate?.hasOpenDialog(pluginId)) { + // One deadline from the start, however often holds come and go. A bus with a + // longer budget of its own has at least that. + deadline ??= setTimeout(() => { + settle(); + reject(new HookTimeoutError(`Hook still waiting on the user after ${Date.now() - started}ms`)); + }, Math.max(0, started + Math.max(HELD_HOOK_DEADLINE_MS, ms) - Date.now())); stopWaiting = dialogGate.whenDialogsClosed(pluginId, arm); return; } + clearTimeout(deadline); reject(new HookTimeoutError(`Hook timed out after ${ms}ms`)); }; const settle = () => { clearTimeout(timer); + clearTimeout(deadline); stopWaiting?.(); }; arm(); diff --git a/lib/plugin-sandbox/host-api-keystore.ts b/lib/plugin-sandbox/host-api-keystore.ts index cb05d215b..f292cd6ad 100644 --- a/lib/plugin-sandbox/host-api-keystore.ts +++ b/lib/plugin-sandbox/host-api-keystore.ts @@ -3,9 +3,9 @@ // private key never reaches the webmail, and the OS's own prompts stay on. // // Privileged tier only (host-api.ts), so a user-uploaded plugin never gets here. -// A privileged frame is same-origin and can reach window.parent.bulwarkDesktop -// directly; these checks are for the front door, and the safeguards that hold in -// both cases are in the shell and the OS. +// None of this is a security boundary: a privileged frame is same-origin and can reach +// window.parent.bulwarkDesktop directly. The checks are for the front door. What holds in +// both cases is in the shell: a native dialog in main (keystore-consent.ts) and the OS. // // RPC errors reach the frame as message strings only, so the code is the message: // keystore:cancelled, keystore:not-found, keystore:unsupported, keystore:locked, @@ -118,8 +118,10 @@ export async function doKeystoreCall(plugin: InstalledPlugin, method: string, ar // The OS may be showing a PIN dialog right now, which the host can't see. While it // is, the plugin is waiting on the user, not hung: this holds the hook timer the // way one of its own dialogs does, so a card's PIN during onRenderEmailBody doesn't - // count towards auto-disable. - const release = holdForUser(plugin.id); + // count towards auto-disable. Not for a list: it never touches a card, and a plugin + // shouldn't be able to stop the timer with it. The hold has an absolute deadline + // (plugin-hooks.ts), and the shell gives up on a stuck call before that. + const release = method === 'keystore.list' ? () => {} : holdForUser(plugin.id); try { return await run(); } catch (error) { diff --git a/lib/plugin-sandbox/runtime.tsx b/lib/plugin-sandbox/runtime.tsx index ff225df55..ed18e5a4e 100644 --- a/lib/plugin-sandbox/runtime.tsx +++ b/lib/plugin-sandbox/runtime.tsx @@ -508,14 +508,15 @@ function buildCoreApi(manifest: PluginManifest) { }, /** * The OS certificate store and smart cards, in the desktop app. Privileged tier - * with the `keystore` permission, when api.host.features.keystore is true. No - * timeout: the OS may be showing a PIN prompt, and the host always answers. - * Errors are bare codes: keystore:cancelled, not-found, unsupported, locked, - * invalid, failed. + * with the `keystore` permission, when api.host.features.keystore is true. The + * shell asks the user first, so any call can answer keystore:cancelled. Key + * operations have no timeout here: the OS may be showing a PIN prompt, and the shell + * gives up by itself. Errors are bare codes: keystore:cancelled, not-found, + * unsupported, locked, invalid, failed. */ keystore: { - /** Certificates fit for mail that have a private key. Never prompts. */ - list: () => callApi('keystore.list', [], 0), + /** Certificates fit for mail that have a private key. The OS doesn't prompt, and it has the usual timeout. */ + list: () => callApi('keystore.list', []), /** Signs a digest. scheme: { scheme: 'rsa-pkcs1' | 'rsa-pss' | 'ecdsa', hash }. RSA gives the raw signature, ECDSA DER. */ sign: (id: string, digest: Uint8Array, scheme: unknown) => callApi('keystore.sign', [id, digest, scheme], 0) as Promise, /** Unwraps a content key. scheme: { scheme: 'rsa-pkcs1' } or { scheme: 'rsa-oaep', hash, label? }. */ diff --git a/locales/ar/common.json b/locales/ar/common.json index 110e14498..1b0d2ba26 100644 --- a/locales/ar/common.json +++ b/locales/ar/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "استخدام الشهادات الموجودة على هذا الكمبيوتر", - "body": "توقيع الرسائل وفك تشفيرها باستخدام الشهادات الموجودة في مخزن الشهادات بهذا الكمبيوتر أو على بطاقة ذكية. يبقى المفتاح الخاص هناك. قد يطلب منك النظام رقم PIN." + "body": "طلب توقيع الرسائل وفك تشفيرها باستخدام الشهادات الموجودة على هذا الكمبيوتر أو على بطاقة ذكية. يسألك التطبيق أولاً. لا يحصل المكوّن الإضافي على المفتاح الخاص أبدًا، ولكن طالما سمحت بذلك يمكنه التوقيع باسمك وقراءة ما يفك تشفيره." }, "email_raw_send": { "title": "إرسال رسائل أنشأتها بنفسها", @@ -6513,5 +6513,13 @@ "server_needed": "أدخل عنوان الخادم أولًا.", "server_declined": "لم تسمح بهذا الخادم، لذا لم يُرسل إليه أي شيء.", "server_invalid": "عنوان الخادم هذا غير صالح." + }, + "desktop_keystore": { + "title": "الشهادات الموجودة على هذا الكمبيوتر", + "description": "يمكن لهذا الخادم أن يطلب استخدام شهاداتك وبطاقاتك الذكية. سيُطلب إذنك مع ذلك قبل استخدام كل شهادة.", + "label": "وصول هذا الخادم", + "label_description": "اسحب إذنك. سيتعين على الخادم أن يطلبه مرة أخرى.", + "revoke": "سحب الإذن", + "revoked": "تم سحب الإذن" } } diff --git a/locales/ca/common.json b/locales/ca/common.json index cdc203867..441e45d82 100644 --- a/locales/ca/common.json +++ b/locales/ca/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Utilitzar els certificats d'aquest ordinador", - "body": "Signa i desxifra missatges amb certificats del magatzem de certificats d'aquest ordinador o d'una targeta intel·ligent. La clau privada es queda allà. El sistema us pot demanar el PIN." + "body": "Demana signar i desxifrar missatges amb certificats d'aquest ordinador o d'una targeta intel·ligent. L'aplicació us ho pregunta abans. El connector no rep mai la clau privada, però mentre ho permeteu pot signar en nom vostre i llegir el que desxifra." }, "email_raw_send": { "title": "Enviar missatges que ha creat ell mateix", @@ -6513,5 +6513,13 @@ "server_needed": "Primer escriu l'adreça del servidor.", "server_declined": "No has permès aquest servidor, així que no s'hi ha enviat res.", "server_invalid": "Aquesta adreça de servidor no és vàlida." + }, + "desktop_keystore": { + "title": "Certificats d'aquest ordinador", + "description": "Aquest servidor pot demanar utilitzar els vostres certificats i targetes intel·ligents. Tot i així, se us preguntarà abans d'utilitzar cada certificat.", + "label": "Accés d'aquest servidor", + "label_description": "Retira el permís. El servidor ho haurà de tornar a demanar.", + "revoke": "Retira", + "revoked": "Permís retirat" } } diff --git a/locales/cs/common.json b/locales/cs/common.json index bed597300..936c0ea36 100644 --- a/locales/cs/common.json +++ b/locales/cs/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Používat certifikáty v tomto počítači", - "body": "Podepisuje a dešifruje zprávy pomocí certifikátů v úložišti certifikátů tohoto počítače nebo na čipové kartě. Soukromý klíč zůstává tam. Systém si může vyžádat váš PIN." + "body": "Žádá o podepisování a dešifrování zpráv pomocí certifikátů v tomto počítači nebo na čipové kartě. Aplikace se vás nejprve zeptá. Plugin nikdy nedostane soukromý klíč, ale dokud to povolíte, může podepisovat vaším jménem a číst, co dešifruje." }, "email_raw_send": { "title": "Odesílat zprávy, které sám vytvořil", @@ -6513,5 +6513,13 @@ "server_needed": "Nejdřív zadejte adresu serveru.", "server_declined": "Tento server jste nepovolili, proto se na něj nic neodeslalo.", "server_invalid": "Tato adresa serveru není platná." + }, + "desktop_keystore": { + "title": "Certifikáty v tomto počítači", + "description": "Tento server může žádat o použití vašich certifikátů a čipových karet. Před použitím každého certifikátu se vás přesto zeptáme.", + "label": "Přístup tohoto serveru", + "label_description": "Odvolejte své povolení. Server se bude muset zeptat znovu.", + "revoke": "Odvolat", + "revoked": "Povolení bylo odvoláno" } } diff --git a/locales/da/common.json b/locales/da/common.json index 5b6f169df..cac3d466f 100644 --- a/locales/da/common.json +++ b/locales/da/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Brug certifikater på denne computer", - "body": "Signer og dekrypter beskeder med certifikater i computerens certifikatlager eller på et chipkort. Den private nøgle bliver, hvor den er. Systemet kan bede om din pinkode." + "body": "Beder om at signere og dekryptere beskeder med certifikater på denne computer eller et chipkort. Appen spørger dig først. Pluginnet får aldrig den private nøgle, men så længe du tillader det, kan det signere i dit navn og læse det, det dekrypterer." }, "email_raw_send": { "title": "Sende beskeder, det selv har lavet", @@ -6513,5 +6513,13 @@ "server_needed": "Angiv først serverens adresse.", "server_declined": "Du tillod ikke denne server, så der blev ikke sendt noget til den.", "server_invalid": "Denne serveradresse er ikke gyldig." + }, + "desktop_keystore": { + "title": "Certifikater på denne computer", + "description": "Denne server kan bede om at bruge dine certifikater og chipkort. Du bliver stadig spurgt, før hvert certifikat bruges.", + "label": "Adgang for denne server", + "label_description": "Træk din tilladelse tilbage. Serveren skal så spørge igen.", + "revoke": "Træk tilbage", + "revoked": "Tilladelsen er trukket tilbage" } } diff --git a/locales/de/common.json b/locales/de/common.json index ece5d1ee3..e24af8ec3 100644 --- a/locales/de/common.json +++ b/locales/de/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Zertifikate auf diesem Computer verwenden", - "body": "Nachrichten mit Zertifikaten aus dem Zertifikatspeicher dieses Computers oder von einer Smartcard signieren und entschlüsseln. Der private Schlüssel bleibt dort. Das System fragt eventuell nach Ihrer PIN." + "body": "Bittet darum, Nachrichten mit Zertifikaten auf diesem Computer oder einer Smartcard zu signieren und zu entschlüsseln. Die App fragt Sie vorher. Das Plugin erhält nie den privaten Schlüssel, kann aber, solange Sie es zulassen, in Ihrem Namen signieren und lesen, was es entschlüsselt." }, "email_raw_send": { "title": "Selbst erstellte Nachrichten senden", @@ -6513,5 +6513,13 @@ "server_needed": "Geben Sie zuerst die Adresse Ihres Servers ein.", "server_declined": "Sie haben diesen Server nicht zugelassen, daher wurde nichts an ihn gesendet.", "server_invalid": "Diese Serveradresse ist ungültig." + }, + "desktop_keystore": { + "title": "Zertifikate auf diesem Computer", + "description": "Dieser Server darf darum bitten, Ihre Zertifikate und Smartcards zu verwenden. Vor der Verwendung eines Zertifikats werden Sie trotzdem gefragt.", + "label": "Zugriff für diesen Server", + "label_description": "Nehmen Sie Ihre Erlaubnis zurück. Der Server muss dann erneut fragen.", + "revoke": "Zurücknehmen", + "revoked": "Erlaubnis zurückgenommen" } } diff --git a/locales/en/common.json b/locales/en/common.json index c2c8bf3cf..7d3d177e9 100644 --- a/locales/en/common.json +++ b/locales/en/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Use certificates on this computer", - "body": "Sign and decrypt messages with certificates in this computer's certificate store or on a smart card. The private key stays there. Your system may ask for your PIN." + "body": "Ask to sign and decrypt messages with certificates on this computer or a smart card. The app asks you first. The plugin never gets the private key, but while you allow it, it can sign in your name and read what it decrypts." }, "email_raw_send": { "title": "Send messages it built itself", @@ -6513,5 +6513,13 @@ "server_needed": "Enter your server's address first.", "server_declined": "You didn't allow this server, so nothing was sent to it.", "server_invalid": "This server address isn't valid." + }, + "desktop_keystore": { + "title": "Certificates on this computer", + "description": "This server may ask to use your certificates and smart cards. You are still asked before each certificate is used.", + "label": "Access for this server", + "label_description": "Take back your permission. The server has to ask again.", + "revoke": "Take back", + "revoked": "Permission taken back" } } diff --git a/locales/es/common.json b/locales/es/common.json index 3696b169b..c003b697d 100644 --- a/locales/es/common.json +++ b/locales/es/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Usar certificados de este ordenador", - "body": "Firma y descifra mensajes con certificados del almacén de certificados de este ordenador o de una tarjeta inteligente. La clave privada se queda allí. El sistema puede pedirle su PIN." + "body": "Pide firmar y descifrar mensajes con certificados de este ordenador o de una tarjeta inteligente. La aplicación le pregunta antes. El complemento nunca recibe la clave privada, pero mientras lo permita puede firmar en su nombre y leer lo que descifra." }, "email_raw_send": { "title": "Enviar mensajes que creó él mismo", @@ -6513,5 +6513,13 @@ "server_needed": "Primero escribe la dirección del servidor.", "server_declined": "No permitiste este servidor, así que no se le envió nada.", "server_invalid": "Esta dirección de servidor no es válida." + }, + "desktop_keystore": { + "title": "Certificados de este ordenador", + "description": "Este servidor puede pedir usar sus certificados y tarjetas inteligentes. Aun así, se le preguntará antes de usar cada certificado.", + "label": "Acceso de este servidor", + "label_description": "Retire su permiso. El servidor tendrá que volver a pedirlo.", + "revoke": "Retirar", + "revoked": "Permiso retirado" } } diff --git a/locales/et/common.json b/locales/et/common.json index ae1e731d9..3384c90a4 100644 --- a/locales/et/common.json +++ b/locales/et/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Kasuta selle arvuti sertifikaate", - "body": "Allkirjastab ja dekrüpteerib sõnumeid selle arvuti sertifikaadihoidlas või kiipkaardil olevate sertifikaatidega. Privaatvõti jääb sinna. Süsteem võib küsida teie PIN-koodi." + "body": "Palub sõnumeid allkirjastada ja dekrüpteerida selle arvuti või kiipkaardi sertifikaatidega. Rakendus küsib teilt enne. Pistikprogramm ei saa kunagi privaatvõtit, kuid seni, kuni seda lubate, saab see teie nimel allkirjastada ja lugeda seda, mida dekrüpteerib." }, "email_raw_send": { "title": "Saada ise koostatud kirju", @@ -6513,5 +6513,13 @@ "server_needed": "Sisesta kõigepealt serveri aadress.", "server_declined": "Sa ei lubanud seda serverit, nii et sellele ei saadetud midagi.", "server_invalid": "See serveri aadress ei kehti." + }, + "desktop_keystore": { + "title": "Selle arvuti sertifikaadid", + "description": "See server võib paluda teie sertifikaate ja kiipkaarte kasutada. Enne iga sertifikaadi kasutamist küsitakse teilt siiski luba.", + "label": "Selle serveri juurdepääs", + "label_description": "Võtke oma luba tagasi. Server peab siis uuesti küsima.", + "revoke": "Võta tagasi", + "revoked": "Luba võeti tagasi" } } diff --git a/locales/fa/common.json b/locales/fa/common.json index a6918dcad..ee6eb5279 100644 --- a/locales/fa/common.json +++ b/locales/fa/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "استفاده از گواهی‌های این رایانه", - "body": "امضا و رمزگشایی پیام‌ها با گواهی‌های موجود در مخزن گواهی این رایانه یا روی کارت هوشمند. کلید خصوصی همان‌جا می‌ماند. ممکن است سیستم PIN شما را بخواهد." + "body": "درخواست امضا و رمزگشایی پیام‌ها با گواهی‌های این رایانه یا کارت هوشمند. برنامه ابتدا از شما می‌پرسد. افزونه هرگز کلید خصوصی را دریافت نمی‌کند، اما تا زمانی که اجازه دهید می‌تواند به نام شما امضا کند و آنچه را رمزگشایی می‌کند بخواند." }, "email_raw_send": { "title": "ارسال پیام‌هایی که خودش ساخته است", @@ -6513,5 +6513,13 @@ "server_needed": "ابتدا نشانی سرور را وارد کنید.", "server_declined": "شما این سرور را مجاز نکردید، پس چیزی به آن فرستاده نشد.", "server_invalid": "این نشانی سرور معتبر نیست." + }, + "desktop_keystore": { + "title": "گواهی‌های این رایانه", + "description": "این سرور می‌تواند برای استفاده از گواهی‌ها و کارت‌های هوشمند شما درخواست دهد. با این حال پیش از استفاده از هر گواهی از شما پرسیده می‌شود.", + "label": "دسترسی این سرور", + "label_description": "اجازهٔ خود را پس بگیرید. سرور باید دوباره درخواست کند.", + "revoke": "پس گرفتن", + "revoked": "اجازه پس گرفته شد" } } diff --git a/locales/fr/common.json b/locales/fr/common.json index cd8e13285..3de5a0882 100644 --- a/locales/fr/common.json +++ b/locales/fr/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Utiliser les certificats de cet ordinateur", - "body": "Signe et déchiffre des messages avec les certificats du magasin de certificats de cet ordinateur ou d'une carte à puce. La clé privée y reste. Le système peut vous demander votre code PIN." + "body": "Demande à signer et déchiffrer des messages avec les certificats de cet ordinateur ou d'une carte à puce. L'application vous pose d'abord la question. Le plugin ne reçoit jamais la clé privée, mais tant que vous l'autorisez, il peut signer en votre nom et lire ce qu'il déchiffre." }, "email_raw_send": { "title": "Envoyer des messages qu'il a lui-même créés", @@ -6513,5 +6513,13 @@ "server_needed": "Saisissez d'abord l'adresse du serveur.", "server_declined": "Vous n'avez pas autorisé ce serveur, donc rien ne lui a été envoyé.", "server_invalid": "Cette adresse de serveur n'est pas valide." + }, + "desktop_keystore": { + "title": "Certificats de cet ordinateur", + "description": "Ce serveur peut demander à utiliser vos certificats et cartes à puce. La question vous est quand même posée avant l'utilisation de chaque certificat.", + "label": "Accès de ce serveur", + "label_description": "Retirez votre autorisation. Le serveur devra la redemander.", + "revoke": "Retirer", + "revoked": "Autorisation retirée" } } diff --git a/locales/he/common.json b/locales/he/common.json index 6d82ad4ac..477e7f931 100644 --- a/locales/he/common.json +++ b/locales/he/common.json @@ -5862,7 +5862,7 @@ }, "keystore": { "title": "שימוש בתעודות במחשב זה", - "body": "חתימה על הודעות ופענוחן באמצעות תעודות ממאגר התעודות של המחשב או מכרטיס חכם. המפתח הפרטי נשאר שם. המערכת עשויה לבקש את קוד ה-PIN שלך." + "body": "מבקש לחתום על הודעות ולפענח אותן באמצעות תעודות במחשב זה או בכרטיס חכם. האפליקציה שואלת אותך קודם. התוסף אף פעם לא מקבל את המפתח הפרטי, אבל כל עוד אתה מאפשר זאת הוא יכול לחתום בשמך ולקרוא את מה שהוא מפענח." }, "email_raw_send": { "title": "שליחת הודעות שהוא בנה בעצמו", @@ -6514,5 +6514,13 @@ "server_needed": "קודם צריך להזין את כתובת השרת.", "server_declined": "לא אישרת את השרת הזה, לכן לא נשלח אליו דבר.", "server_invalid": "כתובת השרת הזו לא תקינה." + }, + "desktop_keystore": { + "title": "תעודות במחשב זה", + "description": "שרת זה יכול לבקש להשתמש בתעודות ובכרטיסים החכמים שלך. עדיין תישאל לפני השימוש בכל תעודה.", + "label": "גישה לשרת זה", + "label_description": "בטל את ההרשאה שלך. השרת יצטרך לבקש שוב.", + "revoke": "ביטול", + "revoked": "ההרשאה בוטלה" } } diff --git a/locales/hu/common.json b/locales/hu/common.json index 94032fde7..e360b332a 100644 --- a/locales/hu/common.json +++ b/locales/hu/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "A számítógépen lévő tanúsítványok használata", - "body": "Üzenetek aláírása és visszafejtése a számítógép tanúsítványtárolójában vagy intelligens kártyán lévő tanúsítványokkal. A titkos kulcs ott marad. A rendszer kérheti a PIN-kódját." + "body": "Kéri, hogy üzeneteket írhasson alá és fejthessen vissza a számítógépen vagy intelligens kártyán lévő tanúsítványokkal. Az alkalmazás előbb megkérdezi Önt. A bővítmény soha nem kapja meg a titkos kulcsot, de amíg engedélyezi, az Ön nevében írhat alá, és elolvashatja, amit visszafejt." }, "email_raw_send": { "title": "Saját maga által összeállított üzenetek küldése", @@ -6513,5 +6513,13 @@ "server_needed": "Először adja meg a szerver címét.", "server_declined": "Nem engedélyezte ezt a szervert, ezért semmit sem küldtünk neki.", "server_invalid": "Ez a szervercím nem érvényes." + }, + "desktop_keystore": { + "title": "A számítógépen lévő tanúsítványok", + "description": "Ez a kiszolgáló kérheti a tanúsítványai és intelligens kártyái használatát. Minden tanúsítvány használata előtt így is megkérdezzük.", + "label": "A kiszolgáló hozzáférése", + "label_description": "Vonja vissza az engedélyét. A kiszolgálónak újra kérnie kell.", + "revoke": "Visszavonás", + "revoked": "Engedély visszavonva" } } diff --git a/locales/it/common.json b/locales/it/common.json index 2f38c56c9..76552fee9 100644 --- a/locales/it/common.json +++ b/locales/it/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Usare i certificati di questo computer", - "body": "Firma e decifra i messaggi con i certificati nell'archivio certificati di questo computer o su una smart card. La chiave privata resta lì. Il sistema può chiedere il PIN." + "body": "Chiede di firmare e decifrare i messaggi con i certificati di questo computer o di una smart card. L'app te lo chiede prima. Il plugin non riceve mai la chiave privata, ma finché lo consenti può firmare a tuo nome e leggere ciò che decifra." }, "email_raw_send": { "title": "Inviare messaggi creati da sé", @@ -6513,5 +6513,13 @@ "server_needed": "Inserisci prima l'indirizzo del server.", "server_declined": "Non hai autorizzato questo server, quindi non gli è stato inviato nulla.", "server_invalid": "Questo indirizzo del server non è valido." + }, + "desktop_keystore": { + "title": "Certificati di questo computer", + "description": "Questo server può chiedere di usare i tuoi certificati e le tue smart card. Ti verrà comunque chiesto prima di usare ogni certificato.", + "label": "Accesso di questo server", + "label_description": "Ritira il tuo permesso. Il server dovrà chiederlo di nuovo.", + "revoke": "Ritira", + "revoked": "Permesso ritirato" } } diff --git a/locales/ja/common.json b/locales/ja/common.json index 248e84546..a749005a6 100644 --- a/locales/ja/common.json +++ b/locales/ja/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "このコンピューターの証明書を使用", - "body": "このコンピューターの証明書ストアまたはスマートカードにある証明書で、メッセージの署名と復号を行います。秘密鍵はそこから出ません。システムが PIN を求める場合があります。" + "body": "このコンピューターまたはスマートカードの証明書でメッセージの署名と復号を行うよう求めます。アプリが先に確認します。プラグインが秘密鍵を受け取ることはありませんが、許可している間はあなたの名前で署名し、復号した内容を読むことができます。" }, "email_raw_send": { "title": "自分で作成したメッセージを送信", @@ -6513,5 +6513,13 @@ "server_needed": "先にサーバーのアドレスを入力してください。", "server_declined": "このサーバーを許可しなかったため、何も送信していません。", "server_invalid": "このサーバーアドレスは正しくありません。" + }, + "desktop_keystore": { + "title": "このコンピューターの証明書", + "description": "このサーバーは、証明書とスマートカードの使用を求めることができます。各証明書を使用する前には、引き続き確認されます。", + "label": "このサーバーのアクセス", + "label_description": "許可を取り消します。サーバーは再度確認する必要があります。", + "revoke": "取り消す", + "revoked": "許可を取り消しました" } } diff --git a/locales/ko/common.json b/locales/ko/common.json index 1ae8135c0..91250257a 100644 --- a/locales/ko/common.json +++ b/locales/ko/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "이 컴퓨터의 인증서 사용", - "body": "이 컴퓨터의 인증서 저장소나 스마트 카드에 있는 인증서로 메시지에 서명하고 메시지를 복호화합니다. 개인 키는 그곳에 그대로 남습니다. 시스템에서 PIN을 요청할 수 있습니다." + "body": "이 컴퓨터나 스마트 카드의 인증서로 메시지 서명과 복호화를 요청합니다. 앱이 먼저 사용자에게 묻습니다. 플러그인은 개인 키를 받지 않지만, 허용하는 동안에는 사용자 이름으로 서명하고 복호화한 내용을 읽을 수 있습니다." }, "email_raw_send": { "title": "직접 만든 메시지 보내기", @@ -6513,5 +6513,13 @@ "server_needed": "먼저 서버 주소를 입력하세요.", "server_declined": "이 서버를 허용하지 않아 아무것도 보내지 않았습니다.", "server_invalid": "이 서버 주소는 올바르지 않습니다." + }, + "desktop_keystore": { + "title": "이 컴퓨터의 인증서", + "description": "이 서버는 인증서와 스마트 카드 사용을 요청할 수 있습니다. 각 인증서를 사용하기 전에는 여전히 사용자에게 묻습니다.", + "label": "이 서버의 액세스", + "label_description": "허용을 취소합니다. 서버는 다시 요청해야 합니다.", + "revoke": "취소", + "revoked": "허용을 취소했습니다" } } diff --git a/locales/lv/common.json b/locales/lv/common.json index 4ae84ab2a..16b5ed5d5 100644 --- a/locales/lv/common.json +++ b/locales/lv/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Izmantot šī datora sertifikātus", - "body": "Paraksta un atšifrē ziņojumus ar sertifikātiem, kas atrodas šī datora sertifikātu krātuvē vai viedkartē. Privātā atslēga paliek tur. Sistēma var prasīt jūsu PIN kodu." + "body": "Lūdz parakstīt un atšifrēt ziņojumus ar šī datora vai viedkartes sertifikātiem. Lietotne vispirms jautā jums. Spraudnis nekad nesaņem privāto atslēgu, taču, kamēr to atļaujat, tas var parakstīt jūsu vārdā un lasīt to, ko atšifrē." }, "email_raw_send": { "title": "Sūtīt paša izveidotas vēstules", @@ -6513,5 +6513,13 @@ "server_needed": "Vispirms ievadiet servera adresi.", "server_declined": "Jūs neatļāvāt šo serveri, tāpēc tam nekas netika nosūtīts.", "server_invalid": "Šī servera adrese nav derīga." + }, + "desktop_keystore": { + "title": "Šī datora sertifikāti", + "description": "Šis serveris var lūgt izmantot jūsu sertifikātus un viedkartes. Pirms katra sertifikāta izmantošanas jums tomēr tiks jautāts.", + "label": "Šī servera piekļuve", + "label_description": "Atsauciet savu atļauju. Serverim būs jālūdz vēlreiz.", + "revoke": "Atsaukt", + "revoked": "Atļauja atsaukta" } } diff --git a/locales/mn/common.json b/locales/mn/common.json index 8711af780..449d09058 100644 --- a/locales/mn/common.json +++ b/locales/mn/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Энэ компьютер дээрх гэрчилгээг ашиглах", - "body": "Энэ компьютерийн гэрчилгээний сан эсвэл ухаалаг картан дээрх гэрчилгээгээр мессежид гарын үсэг зурж, тайлна. Хувийн түлхүүр тэнд хэвээр үлдэнэ. Систем таны PIN кодыг асууж магадгүй." + "body": "Энэ компьютер эсвэл ухаалаг картан дээрх гэрчилгээгээр мессежид гарын үсэг зурах, тайлахыг хүснэ. Апп эхлээд танаас асууна. Өргөтгөл хувийн түлхүүрийг хэзээ ч авахгүй, гэхдээ таныг зөвшөөрч байх хугацаанд таны нэрээр гарын үсэг зурж, тайлсан зүйлээ уншиж чадна." }, "email_raw_send": { "title": "Өөрөө бэлтгэсэн имэйл илгээх", @@ -6513,5 +6513,13 @@ "server_needed": "Эхлээд серверийн хаягаа оруулна уу.", "server_declined": "Та энэ серверийг зөвшөөрөөгүй тул юу ч илгээгээгүй.", "server_invalid": "Энэ серверийн хаяг буруу байна." + }, + "desktop_keystore": { + "title": "Энэ компьютер дээрх гэрчилгээ", + "description": "Энэ сервер таны гэрчилгээ, ухаалаг картыг ашиглахыг хүсэж болно. Гэрчилгээ бүрийг ашиглахын өмнө танаас асуусаар байх болно.", + "label": "Энэ серверийн хандалт", + "label_description": "Зөвшөөрлөө буцааж авна уу. Сервер дахин асуух шаардлагатай.", + "revoke": "Буцааж авах", + "revoked": "Зөвшөөрлийг буцааж авлаа" } } diff --git a/locales/nb/common.json b/locales/nb/common.json index 3a961d552..922d02942 100644 --- a/locales/nb/common.json +++ b/locales/nb/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Bruk sertifikater på denne datamaskinen", - "body": "Signer og dekrypter meldinger med sertifikater i datamaskinens sertifikatlager eller på et smartkort. Den private nøkkelen blir værende der. Systemet kan be om PIN-koden din." + "body": "Ber om å signere og dekryptere meldinger med sertifikater på denne datamaskinen eller et smartkort. Appen spør deg først. Tillegget får aldri den private nøkkelen, men så lenge du tillater det, kan det signere i ditt navn og lese det det dekrypterer." }, "email_raw_send": { "title": "Sende meldinger det har laget selv", @@ -6513,5 +6513,13 @@ "server_needed": "Skriv inn serveradressen først.", "server_declined": "Du tillot ikke denne serveren, så ingenting ble sendt til den.", "server_invalid": "Denne serveradressen er ikke gyldig." + }, + "desktop_keystore": { + "title": "Sertifikater på denne datamaskinen", + "description": "Denne serveren kan be om å bruke sertifikatene og smartkortene dine. Du blir likevel spurt før hvert sertifikat brukes.", + "label": "Tilgang for denne serveren", + "label_description": "Trekk tilbake tillatelsen din. Serveren må da spørre på nytt.", + "revoke": "Trekk tilbake", + "revoked": "Tillatelsen er trukket tilbake" } } diff --git a/locales/nl/common.json b/locales/nl/common.json index a6cf27d93..c8d450062 100644 --- a/locales/nl/common.json +++ b/locales/nl/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Certificaten op deze computer gebruiken", - "body": "Ondertekent en ontsleutelt berichten met certificaten uit het certificaatarchief van deze computer of van een smartcard. De privésleutel blijft daar. Het systeem kan om uw pincode vragen." + "body": "Vraagt om berichten te ondertekenen en te ontsleutelen met certificaten op deze computer of een smartcard. De app vraagt het u eerst. De plug-in krijgt nooit de privésleutel, maar zolang u het toestaat, kan hij in uw naam ondertekenen en lezen wat hij ontsleutelt." }, "email_raw_send": { "title": "Zelf samengestelde berichten verzenden", @@ -6513,5 +6513,13 @@ "server_needed": "Vul eerst het adres van de server in.", "server_declined": "Je hebt deze server niet toegestaan, dus er is niets naartoe gestuurd.", "server_invalid": "Dit serveradres is niet geldig." + }, + "desktop_keystore": { + "title": "Certificaten op deze computer", + "description": "Deze server kan vragen om uw certificaten en smartcards te gebruiken. U krijgt nog steeds een vraag voordat een certificaat wordt gebruikt.", + "label": "Toegang voor deze server", + "label_description": "Trek uw toestemming in. De server moet dan opnieuw vragen.", + "revoke": "Intrekken", + "revoked": "Toestemming ingetrokken" } } diff --git a/locales/pl/common.json b/locales/pl/common.json index 7af5f1555..e2a7d2ed7 100644 --- a/locales/pl/common.json +++ b/locales/pl/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Używanie certyfikatów z tego komputera", - "body": "Podpisuje i odszyfrowuje wiadomości certyfikatami z magazynu certyfikatów tego komputera lub z karty inteligentnej. Klucz prywatny tam pozostaje. System może poprosić o kod PIN." + "body": "Prosi o podpisywanie i odszyfrowywanie wiadomości certyfikatami z tego komputera lub karty inteligentnej. Aplikacja najpierw zapyta. Wtyczka nigdy nie dostaje klucza prywatnego, ale dopóki na to pozwalasz, może podpisywać w Twoim imieniu i czytać to, co odszyfruje." }, "email_raw_send": { "title": "Wysyłanie wiadomości utworzonych przez wtyczkę", @@ -6513,5 +6513,13 @@ "server_needed": "Najpierw wpisz adres serwera.", "server_declined": "Ten serwer nie został dozwolony, więc nic do niego nie wysłano.", "server_invalid": "Ten adres serwera jest nieprawidłowy." + }, + "desktop_keystore": { + "title": "Certyfikaty na tym komputerze", + "description": "Ten serwer może prosić o użycie Twoich certyfikatów i kart inteligentnych. Przed użyciem każdego certyfikatu nadal zostaniesz zapytany.", + "label": "Dostęp tego serwera", + "label_description": "Cofnij swoją zgodę. Serwer będzie musiał zapytać ponownie.", + "revoke": "Cofnij", + "revoked": "Zgoda została cofnięta" } } diff --git a/locales/pt/common.json b/locales/pt/common.json index 11b3b46d2..3c08c38b8 100644 --- a/locales/pt/common.json +++ b/locales/pt/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Usar certificados deste computador", - "body": "Assina e desencripta mensagens com certificados do repositório de certificados deste computador ou de um cartão inteligente. A chave privada fica lá. O sistema pode pedir o seu PIN." + "body": "Pede para assinar e desencriptar mensagens com certificados deste computador ou de um cartão inteligente. A aplicação pergunta-lhe primeiro. O plugin nunca recebe a chave privada, mas enquanto o permitir pode assinar em seu nome e ler o que desencripta." }, "email_raw_send": { "title": "Enviar mensagens criadas por ele mesmo", @@ -6513,5 +6513,13 @@ "server_needed": "Digite primeiro o endereço do servidor.", "server_declined": "Você não permitiu este servidor, então nada foi enviado a ele.", "server_invalid": "Este endereço de servidor não é válido." + }, + "desktop_keystore": { + "title": "Certificados deste computador", + "description": "Este servidor pode pedir para usar os seus certificados e cartões inteligentes. Mesmo assim, ser-lhe-á perguntado antes de cada certificado ser usado.", + "label": "Acesso deste servidor", + "label_description": "Retire a sua permissão. O servidor terá de pedir novamente.", + "revoke": "Retirar", + "revoked": "Permissão retirada" } } diff --git a/locales/ro/common.json b/locales/ro/common.json index 05c47d88e..1d2e29bdd 100644 --- a/locales/ro/common.json +++ b/locales/ro/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Folosește certificatele de pe acest computer", - "body": "Semnează și decriptează mesaje cu certificatele din depozitul de certificate al acestui computer sau de pe un smart card. Cheia privată rămâne acolo. Sistemul vă poate cere codul PIN." + "body": "Cere să semneze și să decripteze mesaje cu certificate de pe acest computer sau de pe un smart card. Aplicația vă întreabă mai întâi. Pluginul nu primește niciodată cheia privată, dar cât timp permiteți poate semna în numele dvs. și poate citi ce decriptează." }, "email_raw_send": { "title": "Trimiterea mesajelor create de el însuși", @@ -6513,5 +6513,13 @@ "server_needed": "Introdu mai întâi adresa serverului.", "server_declined": "Nu ai permis acest server, așa că nu i s-a trimis nimic.", "server_invalid": "Această adresă de server nu este validă." + }, + "desktop_keystore": { + "title": "Certificate de pe acest computer", + "description": "Acest server poate cere să vă folosească certificatele și smart cardurile. Veți fi întrebat în continuare înainte de folosirea fiecărui certificat.", + "label": "Accesul acestui server", + "label_description": "Retrageți permisiunea. Serverul va trebui să ceară din nou.", + "revoke": "Retrage", + "revoked": "Permisiune retrasă" } } diff --git a/locales/ru/common.json b/locales/ru/common.json index 792ef9656..59e1c6a28 100644 --- a/locales/ru/common.json +++ b/locales/ru/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Использовать сертификаты на этом компьютере", - "body": "Подписывает и расшифровывает сообщения сертификатами из хранилища сертификатов этого компьютера или со смарт-карты. Закрытый ключ остаётся там. Система может запросить ваш PIN-код." + "body": "Запрашивает подпись и расшифровку сообщений сертификатами на этом компьютере или смарт-карте. Сначала приложение спросит вас. Плагин никогда не получает закрытый ключ, но пока вы разрешаете, он может подписывать от вашего имени и читать то, что расшифровывает." }, "email_raw_send": { "title": "Отправлять собственные письма", @@ -6513,5 +6513,13 @@ "server_needed": "Сначала введите адрес сервера.", "server_declined": "Вы не разрешили этот сервер, поэтому ему ничего не отправлено.", "server_invalid": "Этот адрес сервера недействителен." + }, + "desktop_keystore": { + "title": "Сертификаты на этом компьютере", + "description": "Этот сервер может запрашивать использование ваших сертификатов и смарт-карт. Перед использованием каждого сертификата вас всё равно спросят.", + "label": "Доступ для этого сервера", + "label_description": "Отзовите своё разрешение. Серверу придётся запросить его снова.", + "revoke": "Отозвать", + "revoked": "Разрешение отозвано" } } diff --git a/locales/sk/common.json b/locales/sk/common.json index 9e815645f..94ce2d1e8 100644 --- a/locales/sk/common.json +++ b/locales/sk/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Používať certifikáty v tomto počítači", - "body": "Podpisuje a dešifruje správy pomocou certifikátov v úložisku certifikátov tohto počítača alebo na čipovej karte. Súkromný kľúč zostáva tam. Systém si môže vyžiadať váš PIN." + "body": "Žiada o podpisovanie a dešifrovanie správ pomocou certifikátov v tomto počítači alebo na čipovej karte. Aplikácia sa vás najprv opýta. Doplnok nikdy nedostane súkromný kľúč, ale kým to povolíte, môže podpisovať vaším menom a čítať, čo dešifruje." }, "email_raw_send": { "title": "Odosielať správy, ktoré sám vytvoril", @@ -6513,5 +6513,13 @@ "server_needed": "Najprv zadajte adresu servera.", "server_declined": "Tento server ste nepovolili, preto sa naň nič neodoslalo.", "server_invalid": "Táto adresa servera nie je platná." + }, + "desktop_keystore": { + "title": "Certifikáty v tomto počítači", + "description": "Tento server môže žiadať o použitie vašich certifikátov a čipových kariet. Pred použitím každého certifikátu sa vás aj tak opýtame.", + "label": "Prístup tohto servera", + "label_description": "Odvolajte svoje povolenie. Server sa bude musieť opýtať znova.", + "revoke": "Odvolať", + "revoked": "Povolenie bolo odvolané" } } diff --git a/locales/th/common.json b/locales/th/common.json index ebca5eeb1..039a179cb 100644 --- a/locales/th/common.json +++ b/locales/th/common.json @@ -2776,7 +2776,7 @@ }, "keystore": { "title": "ใช้ใบรับรองในคอมพิวเตอร์เครื่องนี้", - "body": "ลงนามและถอดรหัสข้อความด้วยใบรับรองในที่เก็บใบรับรองของคอมพิวเตอร์เครื่องนี้หรือบนสมาร์ตการ์ด คีย์ส่วนตัวจะอยู่ที่เดิม ระบบอาจขอ PIN ของคุณ" + "body": "ขอลงนามและถอดรหัสข้อความด้วยใบรับรองในคอมพิวเตอร์เครื่องนี้หรือบนสมาร์ตการ์ด แอปจะถามคุณก่อน ปลั๊กอินไม่ได้รับคีย์ส่วนตัว แต่ตราบใดที่คุณอนุญาต ปลั๊กอินจะลงนามในนามของคุณและอ่านสิ่งที่ถอดรหัสได้" }, "email_raw_send": { "title": "ส่งข้อความที่สร้างขึ้นเอง", @@ -6521,5 +6521,13 @@ "server_needed": "ป้อนที่อยู่เซิร์ฟเวอร์ก่อน", "server_declined": "คุณไม่ได้อนุญาตเซิร์ฟเวอร์นี้ จึงไม่มีการส่งข้อมูลใดไป", "server_invalid": "ที่อยู่เซิร์ฟเวอร์นี้ไม่ถูกต้อง" + }, + "desktop_keystore": { + "title": "ใบรับรองในคอมพิวเตอร์เครื่องนี้", + "description": "เซิร์ฟเวอร์นี้สามารถขอใช้ใบรับรองและสมาร์ตการ์ดของคุณได้ คุณจะยังถูกถามก่อนใช้ใบรับรองแต่ละใบ", + "label": "การเข้าถึงของเซิร์ฟเวอร์นี้", + "label_description": "ถอนการอนุญาตของคุณ เซิร์ฟเวอร์จะต้องขออีกครั้ง", + "revoke": "ถอน", + "revoked": "ถอนการอนุญาตแล้ว" } } diff --git a/locales/tr/common.json b/locales/tr/common.json index 8baf80847..80ac19f74 100644 --- a/locales/tr/common.json +++ b/locales/tr/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Bu bilgisayardaki sertifikaları kullan", - "body": "İletileri bu bilgisayarın sertifika deposundaki veya akıllı karttaki sertifikalarla imzalar ve şifresini çözer. Özel anahtar orada kalır. Sistem PIN kodunuzu isteyebilir." + "body": "Bu bilgisayardaki veya akıllı karttaki sertifikalarla iletileri imzalamak ve şifresini çözmek ister. Uygulama önce size sorar. Eklenti özel anahtarı asla almaz, ancak siz izin verdiğiniz sürece adınıza imzalayabilir ve şifresini çözdüğünü okuyabilir." }, "email_raw_send": { "title": "Kendi oluşturduğu iletileri gönderme", @@ -6513,5 +6513,13 @@ "server_needed": "Önce sunucu adresini girin.", "server_declined": "Bu sunucuya izin vermediniz, bu yüzden ona hiçbir şey gönderilmedi.", "server_invalid": "Bu sunucu adresi geçerli değil." + }, + "desktop_keystore": { + "title": "Bu bilgisayardaki sertifikalar", + "description": "Bu sunucu, sertifikalarınızı ve akıllı kartlarınızı kullanmak isteyebilir. Her sertifika kullanılmadan önce yine de size sorulur.", + "label": "Bu sunucunun erişimi", + "label_description": "İzninizi geri alın. Sunucunun yeniden sorması gerekir.", + "revoke": "Geri al", + "revoked": "İzin geri alındı" } } diff --git a/locales/uk/common.json b/locales/uk/common.json index 165278c4c..e7736acc3 100644 --- a/locales/uk/common.json +++ b/locales/uk/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "Використовувати сертифікати на цьому комп'ютері", - "body": "Підписує та розшифровує повідомлення сертифікатами зі сховища сертифікатів цього комп'ютера або зі смарт-картки. Закритий ключ залишається там. Система може запитати ваш PIN-код." + "body": "Просить підписувати та розшифровувати повідомлення сертифікатами на цьому комп'ютері або смарт-картці. Спершу застосунок запитає вас. Плагін ніколи не отримує закритий ключ, але поки ви дозволяєте, він може підписувати від вашого імені та читати те, що розшифровує." }, "email_raw_send": { "title": "Надсилати власні листи", @@ -6513,5 +6513,13 @@ "server_needed": "Спочатку введіть адресу сервера.", "server_declined": "Ви не дозволили цей сервер, тому йому нічого не надіслано.", "server_invalid": "Ця адреса сервера недійсна." + }, + "desktop_keystore": { + "title": "Сертифікати на цьому комп'ютері", + "description": "Цей сервер може просити використовувати ваші сертифікати та смарт-картки. Перед використанням кожного сертифіката вас однаково запитають.", + "label": "Доступ для цього сервера", + "label_description": "Відкличте свій дозвіл. Серверу доведеться запитати знову.", + "revoke": "Відкликати", + "revoked": "Дозвіл відкликано" } } diff --git a/locales/vi/common.json b/locales/vi/common.json index a94c29c36..05952bcb1 100644 --- a/locales/vi/common.json +++ b/locales/vi/common.json @@ -5873,7 +5873,7 @@ }, "keystore": { "title": "Dùng chứng chỉ trên máy tính này", - "body": "Ký và giải mã thư bằng chứng chỉ trong kho chứng chỉ của máy tính này hoặc trên thẻ thông minh. Khóa riêng vẫn nằm ở đó. Hệ thống có thể yêu cầu mã PIN của bạn." + "body": "Yêu cầu ký và giải mã thư bằng chứng chỉ trên máy tính này hoặc thẻ thông minh. Ứng dụng sẽ hỏi bạn trước. Tiện ích không bao giờ nhận được khóa riêng, nhưng khi bạn cho phép, tiện ích có thể ký dưới tên bạn và đọc nội dung đã giải mã." }, "email_raw_send": { "title": "Gửi thư nó tự tạo", @@ -6525,5 +6525,13 @@ "server_needed": "Trước tiên hãy nhập địa chỉ máy chủ.", "server_declined": "Bạn không cho phép máy chủ này nên không có gì được gửi đến đó.", "server_invalid": "Địa chỉ máy chủ này không hợp lệ." + }, + "desktop_keystore": { + "title": "Chứng chỉ trên máy tính này", + "description": "Máy chủ này có thể yêu cầu dùng chứng chỉ và thẻ thông minh của bạn. Bạn vẫn được hỏi trước khi mỗi chứng chỉ được dùng.", + "label": "Quyền truy cập của máy chủ này", + "label_description": "Thu hồi quyền của bạn. Máy chủ sẽ phải hỏi lại.", + "revoke": "Thu hồi", + "revoked": "Đã thu hồi quyền" } } diff --git a/locales/zh-TW/common.json b/locales/zh-TW/common.json index 67ab7f602..7b78c8dcc 100644 --- a/locales/zh-TW/common.json +++ b/locales/zh-TW/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "使用這台電腦上的憑證", - "body": "使用這台電腦憑證存放區或智慧卡中的憑證為郵件簽署及解密。私密金鑰會一直留在那裡。系統可能會要求輸入 PIN。" + "body": "要求使用這台電腦或智慧卡上的憑證為郵件簽署及解密。應用程式會先詢問你。外掛程式絕不會取得私密金鑰,但在你允許期間,它可以用你的名義簽署,並讀取它解密的內容。" }, "email_raw_send": { "title": "傳送它自行建立的郵件", @@ -6513,5 +6513,13 @@ "server_needed": "請先輸入伺服器位址。", "server_declined": "你沒有允許此伺服器,因此沒有傳送任何內容給它。", "server_invalid": "此伺服器位址無效。" + }, + "desktop_keystore": { + "title": "這台電腦上的憑證", + "description": "此伺服器可以要求使用你的憑證和智慧卡。每次使用憑證前仍會詢問你。", + "label": "此伺服器的存取權", + "label_description": "收回你的許可。伺服器需要重新詢問。", + "revoke": "收回", + "revoked": "已收回許可" } } diff --git a/locales/zh/common.json b/locales/zh/common.json index 0187b30bc..52c1f2f43 100644 --- a/locales/zh/common.json +++ b/locales/zh/common.json @@ -5861,7 +5861,7 @@ }, "keystore": { "title": "使用此电脑上的证书", - "body": "使用此电脑证书存储区或智能卡中的证书为邮件签名和解密。私钥始终保留在那里。系统可能会要求输入 PIN。" + "body": "请求使用此电脑或智能卡上的证书为邮件签名和解密。应用会先询问你。插件永远拿不到私钥,但在你允许期间,它可以以你的名义签名,并读取它解密的内容。" }, "email_raw_send": { "title": "发送它自行构建的邮件", @@ -6513,5 +6513,13 @@ "server_needed": "请先输入服务器地址。", "server_declined": "你没有允许此服务器,因此没有向它发送任何内容。", "server_invalid": "此服务器地址无效。" + }, + "desktop_keystore": { + "title": "此电脑上的证书", + "description": "此服务器可以请求使用你的证书和智能卡。每次使用证书前仍会询问你。", + "label": "此服务器的访问权限", + "label_description": "收回你的许可。服务器需要重新询问。", + "revoke": "收回", + "revoked": "已收回许可" } }