From f9a77c5c27aede4e5978eb55d9b7af781b680a1d Mon Sep 17 00:00:00 2001 From: João Augusto Costa Branco Marado Torres Date: Tue, 24 Jun 2025 12:08:41 -0300 Subject: feat!: initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Augusto Costa Branco Marado Torres --- src/components/BaseHead.astro | 79 +++++++++ src/components/BlogCard.astro | 38 +++++ src/components/Citations.astro | 39 +++++ src/components/Commit.astro | 49 ++++++ src/components/CopyrightNotice.astro | 66 ++++++++ src/components/DateSelector.astro | 141 ++++++++++++++++ src/components/Footer.astro | 62 +++++++ src/components/Header.astro | 41 +++++ src/components/HeaderLink.astro | 18 ++ src/components/Keywords.astro | 52 ++++++ src/components/ReadingTime.astro | 26 +++ src/components/SignaturesTableRows.astro | 51 ++++++ src/components/Translations.astro | 107 ++++++++++++ src/components/licenses/CC.astro | 120 +++++++++++++ src/components/licenses/WTFPL.astro | 53 ++++++ src/components/signature/Authors.astro | 281 +++++++++++++++++++++++++++++++ src/components/signature/Commit.astro | 87 ++++++++++ src/components/signature/Downloads.astro | 63 +++++++ src/components/signature/Signature.astro | 44 +++++ src/components/signature/Summary.astro | 279 ++++++++++++++++++++++++++++++ 20 files changed, 1696 insertions(+) create mode 100644 src/components/BaseHead.astro create mode 100644 src/components/BlogCard.astro create mode 100644 src/components/Citations.astro create mode 100644 src/components/Commit.astro create mode 100644 src/components/CopyrightNotice.astro create mode 100644 src/components/DateSelector.astro create mode 100644 src/components/Footer.astro create mode 100644 src/components/Header.astro create mode 100644 src/components/HeaderLink.astro create mode 100644 src/components/Keywords.astro create mode 100644 src/components/ReadingTime.astro create mode 100644 src/components/SignaturesTableRows.astro create mode 100644 src/components/Translations.astro create mode 100644 src/components/licenses/CC.astro create mode 100644 src/components/licenses/WTFPL.astro create mode 100644 src/components/signature/Authors.astro create mode 100644 src/components/signature/Commit.astro create mode 100644 src/components/signature/Downloads.astro create mode 100644 src/components/signature/Signature.astro create mode 100644 src/components/signature/Summary.astro (limited to 'src/components') diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro new file mode 100644 index 0000000..5ac0410 --- /dev/null +++ b/src/components/BaseHead.astro @@ -0,0 +1,79 @@ +--- +// Import the global.css file here so that it is included on +// all pages through the use of the component. +import "../styles/global.css"; +import { SITE_AUTHOR, SITE_DESCRIPTION, SITE_TITLE } from "../consts"; +import { ClientRouter } from "astro:transitions"; + +export interface Props { + title: string; + description?: string; + image?: string; + keywords?: string[]; +} + +const canonicalURL = new URL(Astro.url.pathname, Astro.site); + +const { title, description = SITE_DESCRIPTION, image, keywords = [] } = + Astro.props; +// const socialImage = image ?? Astro.site.href + 'assets/social.png' +--- + + + + + + + + + + + + + + +{title} + + + +{keywords.length > 0 && } + + + + + + + + +{image && } + + + + + + +{image && } + + + + diff --git a/src/components/BlogCard.astro b/src/components/BlogCard.astro new file mode 100644 index 0000000..7ab42d7 --- /dev/null +++ b/src/components/BlogCard.astro @@ -0,0 +1,38 @@ +--- +import type { CollectionEntry } from "astro:content"; + +interface Props extends CollectionEntry<"blog"> {} + +const { id, data } = Astro.props; +const { title, description, dateCreated, lang } = data; + +const href = `/blog/read/${id}`; +--- + +
+

+ {title} +

+

{description}

+
+ +
+
+ + diff --git a/src/components/Citations.astro b/src/components/Citations.astro new file mode 100644 index 0000000..cc82eda --- /dev/null +++ b/src/components/Citations.astro @@ -0,0 +1,39 @@ +--- +import type { CollectionEntry } from "astro:content"; +import { getEntries } from "astro:content"; + +type Props = { citations: CollectionEntry<"blog">["data"]["relatedPosts"] }; +const citations = await getEntries(Astro.props.citations ?? []); +--- +{ + citations.length > 0 && + ( + + ) +} + + diff --git a/src/components/Commit.astro b/src/components/Commit.astro new file mode 100644 index 0000000..3ee284a --- /dev/null +++ b/src/components/Commit.astro @@ -0,0 +1,49 @@ +--- +import type { Commit } from "@lib/git/types"; +import { gitDir } from "@lib/git"; + +type Props = Commit; + +const { hash, files, author, signature } = Astro.props; + +const git = await gitDir; +--- +

Git commit info:

+
+
Hash
+
{hash}
+
Files
+ {files.map((file) =>
{file.pathname.replace(git, "")}
)} +
Author
+
{author.name} <{author.email}>
+ { + signature && ( +
Commit Signature
+
+
+
Type
+
{signature.type}
+
Signer
+
{signature.signerName}
+
Key fingerprint
+
{signature.keyFingerPrint}
+
+
+ ) + } +
+ + diff --git a/src/components/CopyrightNotice.astro b/src/components/CopyrightNotice.astro new file mode 100644 index 0000000..2aa72ad --- /dev/null +++ b/src/components/CopyrightNotice.astro @@ -0,0 +1,66 @@ +--- +import CC from "./licenses/CC.astro"; +import WTFPL from "./licenses/WTFPL.astro"; +import { CREATIVE_COMMONS_LICENSES, LICENSES } from "../consts.ts"; + +export interface Props { + title: string; + author: string; + email?: string; + website?: string; + dateCreated: Date; + license?: typeof LICENSES[number]; +} + +let { license = "public domain" } = Astro.props; + +let Notice = undefined; +if (license === "WTFPL") { + Notice = WTFPL; +} else if ( + CREATIVE_COMMONS_LICENSES.some((x) => license.localeCompare(x) === 0) +) { + Notice = CC; +} +--- + +{Notice &&
} + +{ + /* +https://spdx.org/licenses/WTFPL.html +https://spdx.org/licenses/GFDL-1.3-or-later.html +https://spdx.org/licenses/FSFAP.html +https://artlibre.org/licence/lal/en/ +https://harmful.cat-v.org/software/ + +IPL-1.0 +IPA +Intel +HPND +EUPL-1.2 +EUPL-1.1 +EUDatagrid +EPL-2.0 +EPL-1.0 +EFL-2.0 +ECL-2.0 +CPL-1.0 +CPAL-1.0 +CDDL-1.0 +BSL-1.0 +BSD-3-Clause +BSD-2-Clause +Artistic-2.0 +APSL-2.0 +Apache-2.0 +Apache-1.1 +AGPL-3.0-or-later +AGPL-3.0-only +AFL-3.0 +AFL-2.1 +AFL-2.0 +AFL-1.2 +AFL-1.1 + */ +} diff --git a/src/components/DateSelector.astro b/src/components/DateSelector.astro new file mode 100644 index 0000000..324bc41 --- /dev/null +++ b/src/components/DateSelector.astro @@ -0,0 +1,141 @@ +--- +interface Props { + date: Date; + years: number[]; + months: number[]; + days?: number[]; +} + +const { date, years, months, days } = Astro.props; + +const y = date.getFullYear(); +const m = date.getMonth() + 1; +const d = date.getDate(); +let yI = 0; +let mI = 0; +let dI = 0; + +const list = new Intl.ListFormat("pt-PT", { type: "unit", style: "narrow" }); + +const pad = (n: number) => String(n).padStart(2, "0"); +--- + + + diff --git a/src/components/Footer.astro b/src/components/Footer.astro new file mode 100644 index 0000000..11c62c4 --- /dev/null +++ b/src/components/Footer.astro @@ -0,0 +1,62 @@ +--- + +--- + + diff --git a/src/components/Header.astro b/src/components/Header.astro new file mode 100644 index 0000000..874a496 --- /dev/null +++ b/src/components/Header.astro @@ -0,0 +1,41 @@ +--- +import HeaderLink from "./HeaderLink.astro"; +--- + +
+

<cravodeabril.pt>

+ +
+

+ +

+

+

+ Esta pesquisa é efectuada pelo Google e utiliza software + proprietário. +

+
+
+ +
diff --git a/src/components/HeaderLink.astro b/src/components/HeaderLink.astro new file mode 100644 index 0000000..8c01f92 --- /dev/null +++ b/src/components/HeaderLink.astro @@ -0,0 +1,18 @@ +--- +import type { HTMLAttributes } from "astro/types"; + +type Props = HTMLAttributes<"a">; + +const { href, class: className, ...props } = Astro.props; +const pathname = Astro.url.pathname; +const isActive = href === pathname; +--- + + + + + diff --git a/src/components/Keywords.astro b/src/components/Keywords.astro new file mode 100644 index 0000000..1800d5a --- /dev/null +++ b/src/components/Keywords.astro @@ -0,0 +1,52 @@ +--- +import type { CollectionEntry } from "astro:content"; + +interface Props { + keywords: CollectionEntry<"blog">["data"]["keywords"]; +} + +const { keywords } = Astro.props; +--- + + + diff --git a/src/components/ReadingTime.astro b/src/components/ReadingTime.astro new file mode 100644 index 0000000..2c8c676 --- /dev/null +++ b/src/components/ReadingTime.astro @@ -0,0 +1,26 @@ +--- +import type { CollectionEntry } from "astro:content"; +import { default as readingTime } from "reading-time"; + +type Props = { + body: CollectionEntry<"blog">["body"]; + lang: CollectionEntry<"blog">["data"]["lang"]; +}; + +const { body, lang } = Astro.props; + +const reading = readingTime(body ?? "", {}); +const minutes = Math.ceil(reading.minutes); +const estimative = new Intl.DurationFormat(lang, { + style: "long", +}).format({ minutes }); +const duration = `PT${ + Math.floor(minutes / 60) > 0 ? Math.floor(minutes / 60) + "H" : "" +}${minutes % 60 > 0 ? minutes % 60 + "M" : ""}`; +--- +

+ Tempo de leitura + estimado: ~ {estimative} + (palavras: {reading.words}) +

diff --git a/src/components/SignaturesTableRows.astro b/src/components/SignaturesTableRows.astro new file mode 100644 index 0000000..eafd4de --- /dev/null +++ b/src/components/SignaturesTableRows.astro @@ -0,0 +1,51 @@ +--- +import { type Summary, VerificationResult } from "@lib/pgp/summary"; +import { PublicKey } from "openpgp"; + +type Props = { summary: Summary; rowspan?: number }; + +const { summary, rowspan } = Astro.props; +const [type, _, info] = summary; + +let name: string = ""; +let email: string = ""; +let fingerprint: string = ""; +let trust: number | undefined = NaN; +let commiter: boolean | undefined = undefined; +let revoked: boolean | undefined = undefined; +let keyType: "primary" | "sub" | "" = ""; + +switch (type) { + case VerificationResult.MISSING_KEY: + fingerprint = typeof info.keyID === "string" + ? info.keyID + : info.keyID.toHex(); + break; + case VerificationResult.TRUSTED_KEY: + const match = info.userID[0].match(/^(.*?)\s*(?:\((.*?)\))?\s*<(.+?)>$/); + + if (match) { + name = match[1]; + email = match[3]; + } + + fingerprint = info.key.getFingerprint(); + trust = info.trust; + keyType = info.key instanceof PublicKey ? "primary" : "sub"; + break; +} + +const names = name.split(/\s/); +const firstName = names[0]; +const lastName = names.length > 1 ? ` ${names[names.length - 1]}` : ""; +--- +{firstName}{lastName} +{email} + + + {`0x${fingerprint.slice(-8)}`} + + +{trust} +{commiter} +{revoked} diff --git a/src/components/Translations.astro b/src/components/Translations.astro new file mode 100644 index 0000000..b0164bb --- /dev/null +++ b/src/components/Translations.astro @@ -0,0 +1,107 @@ +--- +import type { CollectionEntry } from "astro:content"; +import { + getFlagEmojiFromLocale, + getLanguageNameFromLocale, +} from "../utils/lang"; +import { getEntries } from "astro:content"; + +interface Props { + lang: string; + translations?: CollectionEntry<"blog">["data"]["translations"]; +} + +const { lang } = Astro.props; + +const translations = await getEntries(Astro.props.translations ?? []).then( + (translations) => + translations.sort((x, y) => x.data.lang.localeCompare(y.data.lang)), +); +--- + +{ + /* TODO: What about and ? */ +} + +{ + translations.length > 0 && ( + + ) +} + + diff --git a/src/components/licenses/CC.astro b/src/components/licenses/CC.astro new file mode 100644 index 0000000..61f9114 --- /dev/null +++ b/src/components/licenses/CC.astro @@ -0,0 +1,120 @@ +--- +import type { Props as BaseProps } from "../CopyRightNotice.astro"; +interface Props extends BaseProps {} + +let { title, website, author, dateCreated, license } = Astro.props; +const publicdomain = license === "CC0"; +const sa = /SA/.test(license); +const nd = /ND/.test(license); +const nc = /NC/.test(license); +const licenseURL = `https://creativecommons.org/licenses/${ + license.slice(3).toLowerCase() +}/4.0/`; +--- + +
+ { + publicdomain ? ( +

+ + {title} by { + website ? ( + + ) : author + } is marked CC0 1.0 + + + +

+ ) : ( +

+ + {title} © { + dateCreated.getFullYear() + } by { + website ? ( + + ) : author + } is licensed under {license.replace("CC-", "CC ")} 4.0 + + + { + nc && ( + + ) + } + { + sa && ( + <>{" "} + ) + } + { + nd && ( + <>{" "} + ) + } + +

+ ) + } +
diff --git a/src/components/licenses/WTFPL.astro b/src/components/licenses/WTFPL.astro new file mode 100644 index 0000000..feab7ec --- /dev/null +++ b/src/components/licenses/WTFPL.astro @@ -0,0 +1,53 @@ +--- +import type { Props as BaseProps } from "../CopyrightNotice.astro"; +interface Props extends BaseProps {} + +let { website, author, email, dateCreated } = Astro.props; +--- + +
+

+ + Copyright © { + dateCreated.getFullYear() + } + { + website ? ( + + ) : author + } + { + email && ( + <><{email}> + ) + } + +

+

+ + This work is free. You can redistribute it and/or modify it under the + terms of the Do What The Fuck You Want To Public License, Version 2, as + published by Sam Hocevar. See http://www.wtfpl.net/ for more details. + +

+
diff --git a/src/components/signature/Authors.astro b/src/components/signature/Authors.astro new file mode 100644 index 0000000..43a2b36 --- /dev/null +++ b/src/components/signature/Authors.astro @@ -0,0 +1,281 @@ +--- +import { toPK } from "@lib/pgp"; +import { createKeyFromArmor } from "@lib/pgp/create"; +import type { Verification } from "@lib/pgp/verify"; +import { defined, get, instanciate } from "@utils/anonymous"; +import { type CollectionEntry, z } from "astro:content"; +import type { EntityTypesEnum } from "src/consts"; +import qrcode from "yaqrcode"; + +interface Props { + verifications: NonNullable; + expectedSigners: { + entity: CollectionEntry<"entity">; + role: z.infer; + }[]; + commitSignerKey?: string; +} + +const { + verifications: verificationsPromise, + expectedSigners, + commitSignerKey, +} = Astro.props; + +const fingerprintToData = new Map< + string, + { websites: URL[]; role: z.infer } +>(); + +for (const { entity, role } of expectedSigners) { + const key = await createKeyFromArmor(entity.data.publickey.armor); + const fingerprint = key.getFingerprint(); + fingerprintToData.set(fingerprint, { + websites: entity.data.websites?.map(instanciate(URL)) ?? [], + role, + }); +} + +let verifications = await Promise.all( + verificationsPromise.map(async ({ key, keyID, userID, verified }) => { + return { + key: await key, + keyID, + userID: await userID, + verified: await verified.catch(() => false), + }; + }), +); + +const expectedKeys = await Promise.all( + expectedSigners.map(get("entity")).map(({ data }) => + createKeyFromArmor(data.publickey.armor) + ), +); + +const expectedFingerprints = new Set( + expectedKeys.map((key) => key.getFingerprint()), +); + +const verifiedFingerprints = new Set( + verifications.map((v) => v.key).filter(defined).map(toPK).map((key) => + key.getFingerprint() + ), +); + +if (!expectedFingerprints.isSubsetOf(verifiedFingerprints)) { + throw new Error( + `Missing signature from expected signers: ${[ + ...expectedFingerprints.difference(verifiedFingerprints).values(), + ]}`, + ); +} +--- + +
+ + + + + + + + + + + + + + + + + + + + + + { + verifications.map(({ userID, key, keyID, verified }) => { + const fingerprint = key + ? toPK(key).getFingerprint() + : undefined; + const info = fingerprint + ? fingerprintToData.get(fingerprint) + : undefined; + const primary = userID?.[0]; + let role = ""; + switch (info?.role) { + case "author": { + role = "Autor"; + break; + } + case "co-author": { + role = "Co-autor"; + break; + } + case "translator": { + role = "Tradutor"; + break; + } + } + return ( + + + + + + + + ); + }) + } + +
+ Assinaturas +

+ Para verificar uma assinatura é necessário a mensagem, a assinatura digital e as chaves públicas dos assinantes. Esta tabela mostra algumas + informações sobre os assinantes e as suas chaves públicas. +

+
AssinanteFunçãoFingerprintVálidoCommiter
+ + {role} + <>{ + key + ? "0x" + toPK(key).getKeyID().toHex() + : "0x" + keyID.toHex() + } + { + key && false && ( + + ) + } + { + key && + ( + +
+
{toPK(key).armor()}
+
+ ) + } + +
{verified ? "✅" : "❌"} + { + commitSignerKey && + key?.getFingerprint().toUpperCase()?.endsWith( + commitSignerKey.toUpperCase(), + ) && "✅" + } +
+
+ + diff --git a/src/components/signature/Commit.astro b/src/components/signature/Commit.astro new file mode 100644 index 0000000..9cc997a --- /dev/null +++ b/src/components/signature/Commit.astro @@ -0,0 +1,87 @@ +--- +import { gitDir } from "@lib/git"; +import type { Commit } from "@lib/git/types"; +import { toIso8601Full } from "@utils/datetime"; + +type Props = { commit: Commit; lang: string }; + +const dir = await gitDir(); +const { hash, files, author, committer, signature } = + Astro.props.commit; + +const formatter = new Intl.DateTimeFormat([Astro.props.lang], { + dateStyle: "short", + timeStyle: "short", +}); +--- + +
+
+ + Informações sobre o último commit que modificou ficheiros relacionados a + este blog post: + +
+
Hash
+
0x{hash.short.toUpperCase()}
+
Ficheiros modificados
+ { + files.length > 0 + ? files.map((file) => ( +
{file.path.pathname.replace(dir.pathname, "")}
+ )) + :
Nenhum ficheiro modificado
+ } +
+ Autor () +
+
+ {author.name} <{ + author.email + }> +
+
+ Commiter () +
+
+ {committer.name} <{ + committer.email + }> +
+ { + signature && + ( +
Assinatura do commit
+
+
+
Tipo
+
{signature.type}
+
Assinante
+
{signature.signer}
+
Fingerprint da chave
+
0x{signature.key.short}
+
+
+ ) + } +
+
+
+ + diff --git a/src/components/signature/Downloads.astro b/src/components/signature/Downloads.astro new file mode 100644 index 0000000..ac8215f --- /dev/null +++ b/src/components/signature/Downloads.astro @@ -0,0 +1,63 @@ +--- +import { gitDir } from "@lib/git"; +import { get } from "@utils/anonymous"; + +interface Props { + lang: string; +} + +const { lang } = Astro.props; + +let source = new URL( + `${Astro.url.href.replace("read/", "").replace(/\/$/, "")}.md`, +); + +const dir = await gitDir(); + +const format: Intl.NumberFormatOptions = { + notation: "compact", + style: "unit", + unit: "byte", + unitDisplay: "narrow", +}; + +const formatter = new Intl.NumberFormat(lang, format); + +const sourceSize = formatter.format( + await Deno.stat( + new URL("public" + source.pathname, dir), + ).then(get("size")), +); +const sig = await Deno.stat( + new URL("public" + source.pathname + ".sig", dir), +).then(get("size")).catch(() => undefined); +const sigSize = formatter.format(sig); +--- + +
+

Ficheiros para descarregar:

+
+
Blog post
+
+ text/markdown, {sourceSize} +
+ { + sig && ( +
Assinatura digital
+
+ application/pgp-signature, {sigSize} +
+ ) + } +
+
diff --git a/src/components/signature/Signature.astro b/src/components/signature/Signature.astro new file mode 100644 index 0000000..57e9902 --- /dev/null +++ b/src/components/signature/Signature.astro @@ -0,0 +1,44 @@ +--- +import type { Verification } from "@lib/pgp/verify"; +import Summary from "./Summary.astro"; +import Downloads from "./Downloads.astro"; +import Commit from "./Commit.astro"; + +interface Props { + verification: Verification; + lang: string; +} + +const { verification, lang } = Astro.props; +const commit = await verification.commit; +--- + + + + + diff --git a/src/components/signature/Summary.astro b/src/components/signature/Summary.astro new file mode 100644 index 0000000..6ab6bf5 --- /dev/null +++ b/src/components/signature/Summary.astro @@ -0,0 +1,279 @@ +--- +import { + createVerificationSummary, + logLevel, + type Summary, + VerificationResult, +} from "@lib/pgp/summary"; +import type { Verification } from "@lib/pgp/verify"; +import { Level } from "@utils/index"; +import type { NonEmptyArray } from "@utils/iterator"; + +interface Props extends Verification {} + +let [errors, keys] = await createVerificationSummary(Astro.props); +const failed = errors.filter((summary) => "reason" in summary); + +if (failed.length > 0) { + errors = failed as NonEmptyArray; +} + +let worst; + +for (const summary of errors) { + if (worst === undefined) { + worst = summary; + } + + const { result } = summary; + const a = logLevel(worst.result); + const b = logLevel(result); + if (a[0] === b[0] && !a[1] && b[1]) { + worst = summary; + } else if (b[0] === Level.ERROR) { + worst = summary; + } else if (a[0] === Level.OK && b[0] === Level.WARN) { + worst = summary; + } +} + +let lvl: [Level, boolean] | undefined = undefined; + +let label; + +let title = ""; +let content; +const error = worst && "reason" in worst ? worst.reason : undefined; + +if (worst) { + lvl = logLevel(worst.result); + switch (lvl[0]) { + case Level.OK: { + label = "OK"; + break; + } + case Level.WARN: { + label = "Aviso"; + break; + } + case Level.ERROR: { + label = "Erro"; + break; + } + default: { + throw new Error("Unreachable"); + } + } + + switch (worst.result) { + case VerificationResult.NO_SIGNATURE: { + title = "Assinatura não encontrada"; + content = `

+Este blog post não foi assinado. +

+

+Não existe forma de verificar a autentacidade do autor ou a integridade do texto escrito. +

+`; + break; + } + case VerificationResult.MISSING_KEY: { + title = "Chave não encontrada"; + content = `

+Este blog post está assinado digitalmente, porém a chave pública com KeyID 0x${worst.keyID} com que foi assinado não foi encontrada no chaveiro sendo impossível verificar a assinatura, quer dizer, não existe forma de verificar a autentacidade do autor ou a integridade do texto escrito. +

+

+Procure a chave noutro sítio da internet para conseguir fazer a verificação manualmente. +

+`; + break; + } + case VerificationResult.SIGNATURE_CORRUPTED: { + title = "Assinatura corrumpida"; + content = `

+Exite um ficheiro que supostamente é a assinatura, mas ele está corrompido ou com um formato inválido. +

+

+Não existe forma de verificar a autentacidade do autor ou a integridade do texto escrito. +

+`; + break; + } + case VerificationResult.SIGNATURE_COULD_NOT_BE_CHECKED: { + title = "Erro desconhecido"; + content = `

+A assinatura foi encontrada mas ocorreu um erro inesperado durante a verificação. +

+

+Não existe forma de verificar a autentacidade do autor ou a integridade do texto escrito. +

+`; + break; + } + case VerificationResult.BAD_SIGNATURE: { + title = "Assinatura inválida"; + content = `

+Existe uma assinatura digital porém o conteúdo da blog post não corresponde à assinatura. Talvez o texto tenha sido alterado sem ter sido criada uma nova assinatura. +

+

+Pode tentar verificar a assinatura com versões antigas do blog post, mas esta versão não pode ser verificada quanto à autentacidade do autor ou à integridade do texto escrito. +

+`; + break; + } + case VerificationResult.UNTRUSTED_KEY: { + title = "Assinatura válida (chave não confiada)"; + content = `

+A assinatura digital é criptograficamente válida, porém a chave utilizada não é suficientemente confiada pelo servidor. Mas podes ter a certeza que o dono da chave pública é a mesma pessoa que assinou este blog post. +

+`; + break; + } + case VerificationResult.TRUSTED_KEY: { + title = "Assinatura válida"; + content = `

+A assinatura digital é criptograficamente válida. O dono da chave pública é a mesma pessoa que assinou este blog post exatamente como ele está, sem alterações. +

+`; + break; + } + case VerificationResult.EXPIRATION_AFTER_SIGNATURE: { + break; + } + case VerificationResult.EXPIRATION_BEFORE_SIGNATURE: { + break; + } + case VerificationResult.REVOCATION_AFTER_SIGNATURE: { + break; + } + case VerificationResult.REVOCATION_BEFORE_SIGNATURE: { + break; + } + case VerificationResult.KEY_DOES_NOT_SIGN: { + break; + } + default: { + throw new Error("Unreachable"); + } + } +} +--- + +{ + lvl && + ( +
+ {label?.toUpperCase()}: {title.toUpperCase()} + + {error &&
{error}
} +
+ ) +} + + -- cgit v1.2.3