Uverejnený zdroj
Stiahnuť zdroj ZIP
CH-J Tools
Prechádzať adresáre a súbory pre konkrétne vydanie aplikácie.
CH-J / Server Manager
1704 súbory
0.1.2-beta
Pridané: · Postavený:
1
/* Quick-action window: verify or sign one file handed over by the system file manager.2
All checks run in the Core; this page only shows the state and passes the user's choices. */3
"use strict";4
(() => {5
const api = window.quickApi;6
const $ = (id) => document.getElementById(id);7
const T = {8
cs: {9
issuer: "Otisk klíče z podpisu", expectedFp: "Očekávaný otisk klíče (nepovinné, např. z webu vydavatele)", fromKeyserver: "Stáhnout klíč z keys.ch-j.de",10
sourceKeyserver: "keys.ch-j.de (stažen, neuložen)", notOnKeyserver: "Na keys.ch-j.de pro tento otisk žádný veřejný klíč není.", fpMismatch: "Podpis je platný, ale vytvořil ho jiný klíč než zadaný otisk.",11
fpMatched: "Shoduje se se zadaným otiskem", fpInvalid: "Zadejte celý otisk (40 nebo 64 šestnáctkových znaků).",12
useKeyFile: "Ověřit veřejným klíčem ze souboru…", sourceFile: "Veřejný klíč ze souboru (neuložen)", keyFile: "Soubor klíče",13
changed: "Podpis neodpovídá souboru: soubor nebo podpis se po podepsání změnil.", wrongKey: "Tento podpis nevytvořil vybraný klíč. Vyberte veřejný klíč podepisujícího.",14
verifyTitle: "Ověření podpisu", signTitle: "Podepsání souboru", loading: "Načítání…",15
signature: "Podpis", noSignature: "Vedle souboru nebyl nalezen podpis (.asc, .sig, .gpg).", size: "Velikost",16
unlockVerify: "Keychain je zamčený. Ověřeno jen veřejnými klíči uloženými v modulu Hash. Odemkněte Keychain pro ověření i uloženými identitami.",17
unlockSign: "Pro podepsání odemkněte Keychain heslem trezoru.", unlock: "Odemknout", vaultPassword: "Heslo trezoru",18
needsSetup: "Trezor ještě neexistuje. Vytvořte ho v aplikaci a uložte do Keychainu klíč OpenPGP.",19
keysTitle: "Podpisový klíč", noKeys: "V Keychainu není žádný klíč OpenPGP, který by mohl podepisovat (s tajnou částí, neodvolaný a platný).",20
primary: "Hlavní klíč", subkey: "Podklíč", automatic: "Automatický výběr (podle formátu klíče)", expires: "platí do", noExpiry: "bez expirace",21
passphrase: "Heslo klíče", overwrite: "Nahradit existující podpis", sign: "Podepsat", signing: "Podepisuji…", verifying: "Ověřuji…",22
signed: "Soubor je podepsaný", signedTo: "Podpis uložen", valid: "Podpis je platný", invalid: "Podpis není platný",23
signer: "Podepsal", fingerprint: "Otisk klíče", signingKey: "Podpisový klíč", created: "Vytvořeno", source: "Zdroj klíče",24
sourceKeychain: "Keychain (uložená identita)", sourceSaved: "Uložený veřejný klíč (Hash)",25
trustNote: "Platný podpis potvrzuje, že soubor od podpisu nezměnil držitel tohoto klíče. Neověřuje identitu osoby; stav odvolání klíče na keys.ch-j.de je uveden výše.",26
keyMissing: "Podepisující klíč není v Keychainu, mezi uloženými klíči ani na keys.ch-j.de. Zadejte jeho otisk nebo vyberte soubor s veřejným klíčem.",27
openMain: "Otevřít aplikaci", close: "Zavřít", warnRevoked: "Klíč je odvolaný.", warnExpired: "Platnost klíče vypršela.", pq: "PQ",28
keyserver: "keys.ch-j.de", serverPublished: "Zveřejněn", serverRevoked: "Odvolán na serveru", serverMissing: "Na serveru není", serverUnknown: "Nezjištěno",29
checkedNow: "právě ověřeno", checkedAt: "ověřeno", serverStale: "uložený stav, server teď nedostupný", warnServerRevoked: "Keyserver hlásí klíč jako odvolaný."30
},31
de: {32
issuer: "Schlüssel-Fingerabdruck der Signatur", expectedFp: "Erwarteter Fingerabdruck (optional, z. B. von der Website des Herausgebers)", fromKeyserver: "Schlüssel von keys.ch-j.de laden",33
sourceKeyserver: "keys.ch-j.de (geladen, nicht gespeichert)", notOnKeyserver: "keys.ch-j.de hat keinen öffentlichen Schlüssel für diesen Fingerabdruck.", fpMismatch: "Die Signatur ist gültig, stammt aber von einem anderen Schlüssel als dem eingegebenen Fingerabdruck.",34
fpMatched: "Stimmt mit dem eingegebenen Fingerabdruck überein", fpInvalid: "Geben Sie den vollständigen Fingerabdruck ein (40 oder 64 Hexadezimalzeichen).",35
useKeyFile: "Mit öffentlichem Schlüssel aus Datei prüfen…", sourceFile: "Öffentlicher Schlüssel aus Datei (nicht gespeichert)", keyFile: "Schlüsseldatei",36
changed: "Die Signatur passt nicht zur Datei: Datei oder Signatur wurden nach dem Signieren geändert.", wrongKey: "Diese Signatur stammt nicht vom gewählten Schlüssel. Wählen Sie den öffentlichen Schlüssel des Signierenden.",37
verifyTitle: "Signatur prüfen", signTitle: "Datei signieren", loading: "Wird geladen…",38
signature: "Signatur", noSignature: "Neben der Datei wurde keine Signatur gefunden (.asc, .sig, .gpg).", size: "Größe",39
unlockVerify: "Der Schlüsselbund ist gesperrt. Geprüft wurde nur mit im Modul Hash gespeicherten öffentlichen Schlüsseln. Entsperren Sie ihn, um auch gespeicherte Identitäten zu verwenden.",40
unlockSign: "Zum Signieren den Schlüsselbund mit dem Tresorpasswort entsperren.", unlock: "Entsperren", vaultPassword: "Tresorpasswort",41
needsSetup: "Es gibt noch keinen Tresor. Legen Sie ihn in der Anwendung an und speichern Sie einen OpenPGP-Schlüssel.",42
keysTitle: "Signaturschlüssel", noKeys: "Im Schlüsselbund ist kein OpenPGP-Schlüssel, der signieren kann (mit geheimem Teil, nicht widerrufen, gültig).",43
primary: "Hauptschlüssel", subkey: "Unterschlüssel", automatic: "Automatische Auswahl (je nach Schlüsselformat)", expires: "gültig bis", noExpiry: "ohne Ablauf",44
passphrase: "Schlüsselpasswort", overwrite: "Vorhandene Signatur ersetzen", sign: "Signieren", signing: "Signiere…", verifying: "Prüfe…",45
signed: "Datei signiert", signedTo: "Signatur gespeichert", valid: "Signatur gültig", invalid: "Signatur ungültig",46
signer: "Signiert von", fingerprint: "Fingerabdruck", signingKey: "Signaturschlüssel", created: "Erstellt", source: "Schlüsselquelle",47
sourceKeychain: "Schlüsselbund (gespeicherte Identität)", sourceSaved: "Gespeicherter öffentlicher Schlüssel (Hash)",48
trustNote: "Eine gültige Signatur bestätigt, dass die Datei seit dem Signieren durch den Inhaber dieses Schlüssels unverändert ist. Sie bestätigt nicht die Identität der Person; der Widerrufsstatus auf keys.ch-j.de steht oben.",49
keyMissing: "Der signierende Schlüssel ist weder im Schlüsselbund, unter den gespeicherten Schlüsseln noch auf keys.ch-j.de. Geben Sie seinen Fingerabdruck ein oder wählen Sie eine Datei mit dem öffentlichen Schlüssel.",50
openMain: "Anwendung öffnen", close: "Schließen", warnRevoked: "Der Schlüssel ist widerrufen.", warnExpired: "Der Schlüssel ist abgelaufen.", pq: "PQ",51
keyserver: "keys.ch-j.de", serverPublished: "Veröffentlicht", serverRevoked: "Auf dem Server widerrufen", serverMissing: "Nicht auf dem Server", serverUnknown: "Nicht ermittelt",52
checkedNow: "gerade geprüft", checkedAt: "geprüft", serverStale: "gespeicherter Stand, Server gerade nicht erreichbar", warnServerRevoked: "Der Keyserver meldet den Schlüssel als widerrufen."53
},54
en: {55
issuer: "Key fingerprint in the signature", expectedFp: "Expected key fingerprint (optional, e.g. from the publisher's website)", fromKeyserver: "Get the key from keys.ch-j.de",56
sourceKeyserver: "keys.ch-j.de (downloaded, not saved)", notOnKeyserver: "keys.ch-j.de has no public key for this fingerprint.", fpMismatch: "The signature is valid, but it was made with a different key than the fingerprint you entered.",57
fpMatched: "Matches the fingerprint you entered", fpInvalid: "Enter the full fingerprint (40 or 64 hexadecimal characters).",58
useKeyFile: "Verify with a public key file…", sourceFile: "Public key from a file (not saved)", keyFile: "Key file",59
changed: "The signature does not match the file: the file or the signature was changed after signing.", wrongKey: "This signature was not made with the chosen key. Choose the signer's public key.",60
verifyTitle: "Verify signature", signTitle: "Sign file", loading: "Loading…",61
signature: "Signature", noSignature: "No signature (.asc, .sig, .gpg) was found next to the file.", size: "Size",62
unlockVerify: "The Keychain is locked. Only public keys saved in the Hash module were used. Unlock the Keychain to check stored identities too.",63
unlockSign: "Unlock the Keychain with the vault password to sign.", unlock: "Unlock", vaultPassword: "Vault password",64
needsSetup: "There is no vault yet. Create it in the application and store an OpenPGP key in the Keychain.",65
keysTitle: "Signing key", noKeys: "The Keychain has no OpenPGP key that can sign (with a secret part, not revoked, valid).",66
primary: "Primary key", subkey: "Subkey", automatic: "Automatic choice (by key format)", expires: "valid until", noExpiry: "no expiry",67
passphrase: "Key passphrase", overwrite: "Replace the existing signature", sign: "Sign", signing: "Signing…", verifying: "Verifying…",68
signed: "File signed", signedTo: "Signature saved", valid: "Signature is valid", invalid: "Signature is not valid",69
signer: "Signed by", fingerprint: "Key fingerprint", signingKey: "Signing key", created: "Created", source: "Key source",70
sourceKeychain: "Keychain (stored identity)", sourceSaved: "Saved public key (Hash)",71
trustNote: "A valid signature confirms that the holder of this key signed the file and it has not changed since. It does not confirm the person's identity; the revocation status on keys.ch-j.de is shown above.",72
keyMissing: "The signing key is not in the Keychain, among the saved keys or on keys.ch-j.de. Enter its fingerprint or choose a public key file.",73
openMain: "Open application", close: "Close", warnRevoked: "The key is revoked.", warnExpired: "The key has expired.", pq: "PQ",74
keyserver: "keys.ch-j.de", serverPublished: "Published", serverRevoked: "Revoked on the server", serverMissing: "Not on the server", serverUnknown: "Not determined",75
checkedNow: "checked just now", checkedAt: "checked", serverStale: "stored state, server unreachable now", warnServerRevoked: "The keyserver reports the key as revoked."76
}77
};78
let lang = (navigator.language || "en").slice(0, 2);79
const t = (key) => (T[lang] || T.en)[key] || T.en[key] || key;80
const ICONS = {81
verify: '<svg viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8.3-7 10-4-1.7-7-5.5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg>',82
sign: '<svg viewBox="0 0 24 24"><path d="M4 20h16"/><path d="M14.5 4.5l5 5L9 20H4v-5L14.5 4.5z"/></svg>',83
bad: '<svg viewBox="0 0 24 24"><path d="M12 3l7 3v5c0 4.5-3 8.3-7 10-4-1.7-7-5.5-7-10V6l7-3z"/><path d="M10 10l4 4M14 10l-4 4"/></svg>'84
};85
let state = null;86
let chosen = null;88
const message = (error) => String(error && error.message || error).replace(/^.*Error: /s, "");89
const showError = (error) => { $("error").textContent = error ? message(error) : ""; };90
const group = (fp) => String(fp || "").replace(/(.{4})/g, "$1 ").trim();91
const size = (n) => n < 1024 ? `${n} B` : n < 1048576 ? `${(n / 1024).toFixed(1)} KiB` : `${(n / 1048576).toFixed(1)} MiB`;92
const date = (value) => {93
if (value === null || value === undefined || value === "") return "";94
const d = typeof value === "number" ? new Date(value * 1000) : new Date(value);95
return isNaN(d) ? String(value) : d.toLocaleString(lang);96
};97
const el = (tag, cls, text) => { const n = document.createElement(tag); if (cls) n.className = cls; if (text !== undefined) n.textContent = text; return n; };98
const line = (label, value, mono) => { const row = el("div", "line"); row.append(el("span", "", label)); const v = el("span", mono ? "mono" : "", value); row.append(v); return row; };99
const busy = (on) => document.body.classList.toggle("busy", on);101
function badge(kind) {102
$("badge").className = "badge" + (kind === "ok" ? " ok" : kind === "bad" ? " bad" : "");103
$("badge").innerHTML = kind === "bad" ? ICONS.bad : ICONS[state && state.action === "sign" ? "sign" : "verify"];104
}106
function texts() {107
document.documentElement.lang = lang;108
const title = state && state.action === "sign" ? t("signTitle") : state ? t("verifyTitle") : t("loading");109
$("title").textContent = title;110
document.title = `CH-J — ${title}`;111
$("openMain").textContent = t("openMain");112
$("close").textContent = t("close");113
$("unlockButton").textContent = t("unlock");114
$("vaultPassword").placeholder = t("vaultPassword");115
$("keysTitle").textContent = t("keysTitle");116
$("passphraseLabel").textContent = t("passphrase");117
$("overwriteLabel").textContent = t("overwrite");118
$("signButton").textContent = t("sign");119
}121
function renderFile() {122
const file = state.file;123
$("fileBox").hidden = !file;124
if (!file) return;125
$("fileName").textContent = file.name;126
$("filePath").textContent = file.path;127
const extra = [`${t("size")}: ${size(file.size)}`];128
if (state.action === "verify") extra.push(state.signature ? `${t("signature")}: ${state.signature.name}` : t("noSignature"));129
if (state.action === "sign" && state.target) extra.push(`→ ${state.target.name}`);130
$("fileExtra").textContent = extra.join(" · ");131
}133
function renderUnlock(hint) {134
const vault = state.vault || {};135
if (vault.needsSetup) { $("unlockBox").hidden = false; $("unlockForm").hidden = true; $("unlockHint").textContent = t("needsSetup"); return; }136
$("unlockForm").hidden = false;137
$("unlockBox").hidden = vault.unlocked || !vault.initialized;138
$("unlockHint").textContent = hint;139
if (!$("unlockBox").hidden) $("vaultPassword").focus();140
}142
function renderKeys() {143
const box = $("keys");144
box.replaceChildren();145
$("keysBox").hidden = !(state.vault && state.vault.unlocked);146
if ($("keysBox").hidden) return;147
const keys = state.keys || [];148
if (!keys.length) { box.append(el("p", "hint", t("noKeys"))); $("signForm").hidden = true; return; }149
$("signForm").hidden = false;150
$("overwriteRow").hidden = !(state.target && state.target.exists);151
let first = null;152
for (const key of keys) {153
const card = el("div", "identity");154
const uid = (key.userIds || []).map((u) => u.userId || u).filter(Boolean)[0];155
card.append(el("div", "identity-head", key.label || uid || group(key.fingerprint)));156
card.append(el("div", "identity-sub", [uid && uid !== key.label ? uid : "", `V${key.version || "?"}`].filter(Boolean).join(" · ")));157
for (const component of key.components) {158
const row = el("label", "component");159
const radio = el("input");160
radio.type = "radio"; radio.name = "component";161
radio.value = JSON.stringify({ id: key.id, component: component.fingerprint || null });162
radio.addEventListener("change", () => { chosen = JSON.parse(radio.value); $("signButton").disabled = false; $("keyPassphrase").focus(); });163
const meta = el("span", "meta");164
const role = el("span", "role", component.role === "primary" ? t("primary") : component.role === "subkey" ? t("subkey") : t("automatic"));165
if (component.algorithm) role.append(el("span", "tag", String(component.algorithm)));166
if (component.postQuantum) role.append(el("span", "tag", t("pq")));167
meta.append(role);168
if (component.fingerprint) meta.append(el("span", "fp mono", group(component.fingerprint)));169
meta.append(el("span", "fp", component.expiresAt ? `${t("expires")} ${date(component.expiresAt)}` : t("noExpiry")));170
row.append(radio, meta);171
card.append(row);172
first = first || radio;173
}174
box.append(card);175
}176
// One identity with one signing component: preselect it.177
if (keys.length === 1 && keys[0].components.length === 1 && first) { first.checked = true; first.dispatchEvent(new Event("change")); }178
}180
function showResult(kind, title, lines, note) {181
const box = $("result");182
box.className = `result ${kind}`;183
box.replaceChildren(el("h2", "", title), ...lines);184
if (note) box.append(el("p", "note", note));185
box.hidden = false;186
badge(kind === "ok" ? "ok" : kind === "bad" ? "bad" : "");187
}189
function showVerified(r) {190
const uid = (r.userIds || []).map((u) => u.userId || u).filter(Boolean).join(", ");191
const lines = [];192
if (uid || r.label) lines.push(line(t("signer"), uid || r.label));193
lines.push(line(t("fingerprint"), group(r.primaryFingerprint), true));194
if (r.signerFingerprint && r.signerFingerprint !== r.primaryFingerprint) lines.push(line(t("signingKey"), group(r.signerFingerprint), true));195
if (r.createdAt) lines.push(line(t("created"), date(r.createdAt)));196
lines.push(line(t("source"), r.keySource === "keychain" ? t("sourceKeychain") : r.keySource === "file" ? t("sourceFile") : r.keySource === "keyserver" ? t("sourceKeyserver") : t("sourceSaved")));197
if (r.expectedMatched === true || r.fingerprintMatched === true) lines.push(line(t("fingerprint"), t("fpMatched")));198
if (r.keyFile) lines.push(line(t("keyFile"), r.keyFile));199
// Keyserver state chosen by freshness in the Core (stored if fresh enough, otherwise checked now).200
const s = r.server;201
if (s && s.known) {202
const status = s.found !== true && s.found !== false ? t("serverUnknown") : s.revoked === true ? t("serverRevoked") : s.found ? t("serverPublished") : t("serverMissing");203
const when = s.live ? t("checkedNow") : s.verifiedAt ? `${t("checkedAt")} ${date(s.verifiedAt)}` : "";204
lines.push(line(t("keyserver"), [status, when, s.fresh === false ? t("serverStale") : ""].filter(Boolean).join(" · ")));205
}206
const warn = [r.revoked === true ? t("warnRevoked") : "", s && s.revoked === true && r.revoked !== true ? t("warnServerRevoked") : "", r.expired === true ? t("warnExpired") : ""].filter(Boolean).join(" ");207
showResult(warn ? "warn" : "ok", t("valid"), lines, [warn, t("trustNote")].filter(Boolean).join(" "));208
}210
// Failure text and whether another public key can still help (unknown signer or a wrong chosen key).211
function failure(error) {212
const text = message(error);213
if (/QUICK_SIGNATURE_INVALID|does not match this file/i.test(text)) return { text: t("changed"), keyHelps: false };214
if (/FINGERPRINT_MISMATCH|different key than the fingerprint/i.test(text)) return { text: t("fpMismatch"), keyHelps: true };215
if (/FINGERPRINT_INVALID|full fingerprint/i.test(text)) return { text: t("fpInvalid"), keyHelps: true };216
if (/KEYSERVER_NOT_FOUND|no public key for this fingerprint/i.test(text)) return { text: t("notOnKeyserver"), keyHelps: true };217
if (/SIGNER_UNKNOWN|not made with/i.test(text)) return { text: t("wrongKey"), keyHelps: true };218
if (/fingerprint alone|key required|KEY_REQUIRED/i.test(text)) return { text: t("keyMissing"), keyHelps: true };219
return { text, keyHelps: false };220
}222
function showFailure(error, typed) {223
const { text, keyHelps } = failure(error);224
const issuer = (state.issuers || []).find((i) => i.length === 40 || i.length === 64) || (state.issuers || [])[0];225
showResult("bad", t("invalid"), issuer ? [line(t("issuer"), group(issuer), true)] : [], text);226
if (!keyHelps) return;227
const box = el("div", "key-options");228
const label = el("label", "expected", t("expectedFp"));229
const input = el("input");230
input.id = "expectedFingerprint"; input.className = "mono"; input.autocomplete = "off"; input.spellcheck = false;231
input.value = typed || "";232
label.append(input);233
const fromServer = el("button", "button", t("fromKeyserver"));234
fromServer.type = "button"; fromServer.id = "verifyKeyserver";235
fromServer.addEventListener("click", () => verifyWith("keyserver", input.value));236
const fromFile = el("button", "button", t("useKeyFile"));237
fromFile.type = "button"; fromFile.id = "verifyWithKey";238
fromFile.addEventListener("click", () => verifyWith("file", input.value));239
const row = el("div", "row");240
row.append(fromServer, fromFile);241
box.append(label, row);242
$("result").append(box);243
}245
async function verifyWith(source, typed) {246
showError(null); busy(true);247
try {248
const r = source === "keyserver" ? await api.verifyWithKeyserver(typed) : await api.verifyWithKey(typed);249
if (r && r.cancelled) return;250
showVerified(r);251
} catch (error) { showFailure(error, typed); }252
finally { busy(false); }253
}255
async function verify() {256
if (!state.signature) { showResult("bad", t("invalid"), [], t("noSignature")); return; }257
showResult("", t("verifying"), []);258
try { showVerified(await api.verify()); }259
catch (error) { showFailure(error); }260
renderUnlock(t("unlockVerify"));261
}263
async function load() {264
showError(null);265
try {266
state = await api.state();267
lang = T[state.language] ? state.language : T[String(state.language || "").slice(0, 2)] ? String(state.language).slice(0, 2) : "en";268
texts(); badge(""); renderFile();269
if (state.action === "verify") { $("keysBox").hidden = true; await verify(); }270
else { renderUnlock(t("unlockSign")); renderKeys(); }271
} catch (error) { texts(); badge("bad"); showError(error); }272
}274
$("unlockForm").addEventListener("submit", async (event) => {275
event.preventDefault();276
showError(null); busy(true);277
try { await api.unlock($("vaultPassword").value); $("vaultPassword").value = ""; await load(); }278
catch (error) { showError(error); $("vaultPassword").select(); }279
finally { busy(false); }280
});282
$("signForm").addEventListener("submit", async (event) => {283
event.preventDefault();284
if (!chosen) return;285
showError(null); busy(true);286
$("signButton").textContent = t("signing");287
try {288
const r = await api.sign({ id: chosen.id, component: chosen.component, passphrase: $("keyPassphrase").value, overwrite: $("overwrite").checked });289
$("keyPassphrase").value = "";290
$("keysBox").hidden = true; $("unlockBox").hidden = true;291
showResult("ok", t("signed"), [line(t("signedTo"), r.signature.path, true)]);292
} catch (error) { showError(error); $("keyPassphrase").select(); }293
finally { busy(false); $("signButton").textContent = t("sign"); }294
});296
$("close").addEventListener("click", () => api.close());297
$("openMain").addEventListener("click", () => api.openMain().catch(showError));298
document.addEventListener("keydown", (event) => { if (event.key === "Escape") api.close(); });299
texts();300
load();301
})();SHA-256
a05a0cf7854a49c430d95332670b9fffb264370dc217624761d93b887862acc5CH-J Proprietary Software License 1.14 · Zdroj sa poskytuje v rámci CH-J Proprietary Software License 1.14. Jeho dostupnosť nemení licenčné podmienky ani neposkytuje dodatočné práva.
Archív SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f