feat(new tool): pdf signature checker (#745)

This commit is contained in:
Corentin THOMASSET 2023-11-12 23:22:41 +01:00 committed by GitHub
parent 205e360400
commit 478192065e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 448 additions and 21 deletions

View file

@ -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>

View file

@ -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',

View file

@ -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>

View file

@ -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'),
});

View file

@ -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');
});
});

View file

@ -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
}
}
}

View file

@ -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>