diff options
author | 2023-11-12 23:22:41 +0100 | |
---|---|---|
committer | 2023-11-12 22:22:41 +0000 | |
commit | 478192065e6a251579a08089235ad1f26e0cc9c9 (patch) | |
tree | b309be21ca831eaf38436f78d06101f50cec401c /src/tools | |
parent | 205e360400d588a5f9ddc9595fca29db68a07bf6 (diff) | |
download | it-tools-478192065e6a251579a08089235ad1f26e0cc9c9.tar.gz it-tools-478192065e6a251579a08089235ad1f26e0cc9c9.tar.zst it-tools-478192065e6a251579a08089235ad1f26e0cc9c9.zip |
feat(new tool): pdf signature checker (#745)
Diffstat (limited to 'src/tools')
7 files changed, 226 insertions, 3 deletions
diff --git a/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue b/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue index 647be98..6844dc5 100644 --- a/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue +++ b/src/tools/iban-validator-and-parser/iban-validator-and-parser.vue @@ -60,9 +60,11 @@ const ibanExamples = [ <div> <c-input-text v-model:value="rawIban" placeholder="Enter an IBAN to check for validity..." test-id="iban-input" /> - <c-key-value-list :items="ibanInfo" my-5 data-test-id="iban-info" /> + <c-card v-if="ibanInfo.length > 0" mt-5> + <c-key-value-list :items="ibanInfo" data-test-id="iban-info" /> + </c-card> - <c-card title="Valid IBAN examples"> + <c-card title="Valid IBAN examples" mt-5> <div v-for="iban in ibanExamples" :key="iban"> <c-text-copyable :value="iban" font-mono :displayed-value="friendlyFormatIBAN(iban)" /> </div> diff --git a/src/tools/index.ts b/src/tools/index.ts index fa63d3d..52bdf8e 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as pdfSignatureChecker } from './pdf-signature-checker'; import { tool as numeronymGenerator } from './numeronym-generator'; import { tool as macAddressGenerator } from './mac-address-generator'; import { tool as textToBinary } from './text-to-binary'; @@ -78,7 +79,7 @@ import { tool as xmlFormatter } from './xml-formatter'; export const toolsByCategory: ToolCategory[] = [ { name: 'Crypto', - components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser], + components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker], }, { name: 'Converter', diff --git a/src/tools/pdf-signature-checker/components/pdf-signature-details.vue b/src/tools/pdf-signature-checker/components/pdf-signature-details.vue new file mode 100644 index 0000000..c39f616 --- /dev/null +++ b/src/tools/pdf-signature-checker/components/pdf-signature-details.vue @@ -0,0 +1,99 @@ +<script setup lang="ts"> +import type { SignatureInfo } from '../pdf-signature-checker.types'; + +const props = defineProps<{ signature: SignatureInfo }>(); +const { signature } = toRefs(props); + +const tableHeaders = { + validityPeriod: 'Validity period', + issuedBy: 'Issued by', + issuedTo: 'Issued to', + pemCertificate: 'PEM certificate', +}; + +const certs = computed(() => signature.value.meta.certs.map((certificate, index) => ({ + ...certificate, + validityPeriod: { + notBefore: new Date(certificate.validityPeriod.notBefore).toLocaleString(), + notAfter: new Date(certificate.validityPeriod.notAfter).toLocaleString(), + }, + certificateName: `Certificate ${index + 1}`, +})), +); +</script> + +<template> + <div flex flex-col gap-2> + <c-table :data="certs" :headers="tableHeaders"> + <template #validityPeriod="{ value }"> + <c-key-value-list + :items="[{ + label: 'Not before', + value: value.notBefore, + }, { + label: 'Not after', + value: value.notAfter, + }]" + /> + </template> + + <template #issuedBy="{ value }"> + <c-key-value-list + :items="[{ + label: 'Common name', + value: value.commonName, + }, { + label: 'Organization name', + value: value.organizationName, + }, { + label: 'Country name', + value: value.countryName, + }, { + label: 'Locality name', + value: value.localityName, + }, { + label: 'Organizational unit name', + value: value.organizationalUnitName, + }, { + label: 'State or province name', + value: value.stateOrProvinceName, + }]" + /> + </template> + + <template #issuedTo="{ value }"> + <c-key-value-list + :items="[{ + label: 'Common name', + value: value.commonName, + }, { + label: 'Organization name', + value: value.organizationName, + }, { + label: 'Country name', + value: value.countryName, + }, { + label: 'Locality name', + value: value.localityName, + }, { + label: 'Organizational unit name', + value: value.organizationalUnitName, + }, { + label: 'State or province name', + value: value.stateOrProvinceName, + }]" + /> + </template> + + <template #pemCertificate="{ value }"> + <c-modal-value :value="value" label="View PEM cert"> + <template #value> + <div break-all text-xs> + {{ value }} + </div> + </template> + </c-modal-value> + </template> + </c-table> + </div> +</template> diff --git a/src/tools/pdf-signature-checker/index.ts b/src/tools/pdf-signature-checker/index.ts new file mode 100644 index 0000000..5456397 --- /dev/null +++ b/src/tools/pdf-signature-checker/index.ts @@ -0,0 +1,12 @@ +import { defineTool } from '../tool'; +import FileCertIcon from '~icons/mdi/file-certificate-outline'; + +export const tool = defineTool({ + name: 'PDF signature checker', + path: '/pdf-signature-checker', + description: 'Verify the signatures of a PDF file. A signed PDF file contains one or more signatures that may be used to determine whether the contents of the file have been altered since the file was signed.', + keywords: ['pdf', 'signature', 'checker', 'verify', 'validate', 'sign'], + component: () => import('./pdf-signature-checker.vue'), + icon: FileCertIcon, + createdAt: new Date('2023-12-09'), +}); diff --git a/src/tools/pdf-signature-checker/pdf-signature-checker.e2e.spec.ts b/src/tools/pdf-signature-checker/pdf-signature-checker.e2e.spec.ts new file mode 100644 index 0000000..8e9a287 --- /dev/null +++ b/src/tools/pdf-signature-checker/pdf-signature-checker.e2e.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test'; + +test.describe('Tool - Pdf signature checker', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/pdf-signature-checker'); + }); + + test('Has correct title', async ({ page }) => { + await expect(page).toHaveTitle('PDF signature checker - IT Tools'); + }); +}); diff --git a/src/tools/pdf-signature-checker/pdf-signature-checker.types.ts b/src/tools/pdf-signature-checker/pdf-signature-checker.types.ts new file mode 100644 index 0000000..6932509 --- /dev/null +++ b/src/tools/pdf-signature-checker/pdf-signature-checker.types.ts @@ -0,0 +1,39 @@ +export interface SignatureInfo { + verified: boolean + authenticity: boolean + integrity: boolean + expired: boolean + meta: { + certs: { + clientCertificate?: boolean + issuedBy: { + commonName: string + organizationalUnitName?: string + organizationName: string + countryName?: string + localityName?: string + stateOrProvinceName?: string + } + issuedTo: { + commonName: string + serialNumber?: string + organizationalUnitName?: string + organizationName: string + countryName?: string + localityName?: string + stateOrProvinceName?: string + } + validityPeriod: { + notBefore: string + notAfter: string + } + pemCertificate: string + }[] + signatureMeta: { + reason: string + contactInfo: string | null + location: string + name: string | null + } + } +} diff --git a/src/tools/pdf-signature-checker/pdf-signature-checker.vue b/src/tools/pdf-signature-checker/pdf-signature-checker.vue new file mode 100644 index 0000000..0220616 --- /dev/null +++ b/src/tools/pdf-signature-checker/pdf-signature-checker.vue @@ -0,0 +1,59 @@ +<script setup lang="ts"> +import verifyPDF from 'pdf-signature-reader'; +import type { SignatureInfo } from './pdf-signature-checker.types'; +import { formatBytes } from '@/utils/convert'; + +const signatures = ref<SignatureInfo[]>([]); +const status = ref<'idle' | 'parsed' | 'error' | 'loading'>('idle'); +const file = ref<File | null>(null); + +async function onVerifyClicked(uploadedFile: File) { + file.value = uploadedFile; + const fileBuffer = await uploadedFile.arrayBuffer(); + + status.value = 'loading'; + try { + const { signatures: parsedSignatures } = verifyPDF(fileBuffer); + signatures.value = parsedSignatures; + status.value = 'parsed'; + } + catch (e) { + signatures.value = []; + status.value = 'error'; + } +} +</script> + +<template> + <div style="flex: 0 0 100%"> + <div mx-auto max-w-600px> + <c-file-upload title="Drag and drop a PDF file here, or click to select a file" accept=".pdf" @file-upload="onVerifyClicked" /> + + <c-card v-if="file" mt-4 flex gap-2> + <div font-bold> + {{ file.name }} + </div> + + <div> + {{ formatBytes(file.size) }} + </div> + </c-card> + + <div v-if="status === 'error'"> + <c-alert mt-4> + No signatures found in the provided file. + </c-alert> + </div> + </div> + </div> + + <div v-if="status === 'parsed' && signatures.length" style="flex: 0 0 100%" mt-5 flex flex-col gap-4> + <div v-for="(signature, index) of signatures" :key="index"> + <div mb-2 font-bold> + Signature {{ index + 1 }} certificates : + </div> + + <pdf-signature-details :signature="signature" /> + </div> + </div> +</template> |