Skip to content

Commit

Permalink
feat(new tool): Certificate/Key Parser and infos
Browse files Browse the repository at this point in the history
Parse Certificate and Keys (Public, Private, Signature, Fingerprint...) and displays infos
Fix #671
  • Loading branch information
sharevb committed Mar 3, 2024
1 parent e073b2b commit 4adff00
Show file tree
Hide file tree
Showing 4 changed files with 381 additions and 3 deletions.
14 changes: 12 additions & 2 deletions src/components/InputCopyable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import { useVModel } from '@vueuse/core';
import { useCopy } from '@/composable/copy';
const props = defineProps<{ value: string }>();
const props = defineProps<{
value: string
multiline?: boolean
rows?: number | string
autosize?: boolean
}>();
const emit = defineEmits(['update:value']);
const value = useVModel(props, 'value', emit);
Expand All @@ -11,7 +16,12 @@ const tooltipText = computed(() => isJustCopied.value ? 'Copied!' : 'Copy to cli
</script>

<template>
<c-input-text v-model:value="value">
<c-input-text
v-model:value="value"
:multiline="multiline"
:rows="rows"
:autosize="autosize"
>
<template #suffix>
<c-tooltip :tooltip="tooltipText">
<c-button circle variant="text" size="small" @click="copy()">
Expand Down
342 changes: 342 additions & 0 deletions src/tools/certificate-key-parser/certificate-key-parser.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
<script setup lang="ts">
import {
parseCertificate, parseFingerprint, parseKey, parsePrivateKey, parseSignature,
type CertificateFormat,
type AlgorithmType,
type SignatureFormatType
} from 'sshpk';
import { getExtensionFromMimeType, getMimeTypeFromBase64, useDownloadFileFromBase64 } from '@/composable/downloadBase64';
import { Base64 } from 'js-base64';
function buf2Hex(buffer: ArrayBuffer) { // buffer is an ArrayBuffer
return [...new Uint8Array(buffer)]
.map(x => x.toString(16).padStart(2, '0'))
.join('');
}
const inputKeyOrCertificate = ref('');
const passphrase = ref('');
const fileInput = ref() as Ref<Buffer>;
async function onUpload(file: File) {
if (file) {
fileInput.value = Buffer.from(await file.arrayBuffer());
inputKeyOrCertificate.value = '';
}
}
const certificateX509DER = ref('');
const { download: downloadX509DER } = useDownloadFileFromBase64(
{
source: certificateX509DER,
extension: "der",
});
function downloadX509DERFile() {
if (certificateX509DER.value === '') {
return;
}
try {
downloadX509DER();
}
catch (_) {
//
}
}
type LabelValue = {
label: string
value: string
multiline?: boolean
};
const parsedSections = computed<LabelValue[]>(() => {
try {
certificateX509DER.value='';
const onErrorReturnErrorMessage = <T>(func: () => T) => {
try {
return func();
}
catch (e:any) {
return e.toString();
}
};
const canParse = <T>(value: string | Buffer, parseFunction: (value: string | Buffer) => T) => {
try {
return parseFunction(value);
}
catch {
return null;
}
};
const inputKeyOrCertificateValue =
inputKeyOrCertificate.value !== '' ?
inputKeyOrCertificate.value :
fileInput.value;
const publicKey = canParse(inputKeyOrCertificateValue, parseKey);
if (publicKey) {
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Public Key',
},
{
label: 'Key Type: ',
value: publicKey.type,
},
{
label: 'Size: ',
value: publicKey.size,
},
{
label: 'Comment: ',
value: publicKey.comment,
multiline: true,
},
{
label: 'Curve: ',
value: publicKey.curve ?? 'none',
},
{
label: 'Fingerprint (sha256): ',
value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha256')),
multiline: true,
},
{
label: 'Fingerprint (sha512): ',
value: onErrorReturnErrorMessage(() => publicKey.fingerprint('sha512')),
multiline: true,
},
];
}
const privateKey = canParse(inputKeyOrCertificateValue,
(value) => parsePrivateKey(value, 'auto', {passphrase: passphrase.value}));
if (privateKey) {
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Private Key',
},
{
label: 'Key Type: ',
value: privateKey.type,
},
{
label: 'Size: ',
value: privateKey.size,
},
{
label: 'Comment: ',
value: privateKey.comment,
multiline: true,
},
{
label: 'Curve: ',
value: privateKey.curve,
},
{
label: 'Fingerprint (sha256): ',
value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha256')),
multiline: true,
},
{
label: 'Fingerprint (sha512): ',
value: onErrorReturnErrorMessage(() => privateKey.fingerprint('sha512')),
multiline: true,
},
];
}
const cert = canParse(inputKeyOrCertificateValue, (value => {
for (const format of ["openssh" , "pem" , "x509"]){
try {
return parseCertificate(value, format as CertificateFormat);
} catch{}
}
return null;
}));
if (cert) {
try {
certificateX509DER.value = Base64.fromUint8Array(cert.toBuffer('x509'));
} catch {}
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Certificate',
},
{
label: 'Subjects: ',
value: cert.subjects.map(s => s.toString()).join('\n'),
multiline: true,
},
{
label: 'Issuer: ',
value: cert.issuer.toString(),
multiline: true,
},
{
label: 'Subject Key: ',
value: onErrorReturnErrorMessage(() => cert.subjectKey?.toString('ssh')),
multiline: true,
},
{
label: 'Subject Key Type: ',
value: cert.subjectKey?.type,
},
{
label: 'Subject Size: ',
value: cert.subjectKey?.size,
},
{
label: 'Subject Comment: ',
value: cert.subjectKey?.comment,
multiline: true,
},
{
label: 'Subject Curve: ',
value: cert.subjectKey?.curve ?? 'none',
},
{
label: 'Issuer Key: ',
value: onErrorReturnErrorMessage(() => cert.issuerKey?.toString('ssh')),
multiline: true,
},
{
label: 'Serial: ',
value: buf2Hex(cert.serial),
},
{
label: 'Purposes: ',
value: cert.purposes?.join(', '),
},
{
label: 'Extensions: ',
value: JSON.stringify(cert.getExtensions(), null, 2),
multiline: true,
},
{
label: 'Fingerprint (sha256): ',
value: onErrorReturnErrorMessage(() => cert.fingerprint('sha256')),
multiline: true,
},
{
label: 'Fingerprint (sha512): ',
value: onErrorReturnErrorMessage(() => cert.fingerprint('sha512')),
multiline: true,
},
{
label: 'Certificate (pem): ',
value: onErrorReturnErrorMessage(() => cert.toString('pem')),
multiline: true,
},
];
}
const fingerprint = canParse(inputKeyOrCertificateValue, (value) => parseFingerprint(value.toString()));
if (fingerprint) {
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Fingerprint',
},
{
label: 'Fingerprint (hex): ',
value: fingerprint.toString('hex'),
},
{
label: 'Fingerprint (base64): ',
value: fingerprint.toString('base64'),
},
];
}
const signature = canParse(inputKeyOrCertificateValue, (value => {
//
for (const algo of ["dsa" , "rsa" , "ecdsa" , "ed25519"]){
for (const format of ["asn1" , "ssh" , "raw"]){
try {
return parseSignature(value, algo as AlgorithmType, format as SignatureFormatType);
} catch{}
}
}
return null;
}));
if (signature) {
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Signature',
},
{
label: 'Fingerprint (asn1): ',
value: signature.toString('asn1'),
},
{
label: 'Fingerprint (ssh): ',
value: signature.toString('ssh'),
},
];
}
return <LabelValue[]>[
{
label: 'Type: ',
value: 'Unknown format or invalid passphrase',
}];
}
catch (e: any) {
return <LabelValue[]>[
{
label: 'Error: ',
value: e.toString(),
}];
}
});
</script>

<template>
<div>
<c-card>
<c-file-upload title="Drag and drop a Certificate file here, or click to select a Certificate file" @file-upload="onUpload" />
<p>-OR-</p>
<c-input-text
v-model:value="inputKeyOrCertificate"
label="Paste your Public Key / Private Key / Signature / Fingerprint / Certificate:"
placeholder="Your Public Key / Private Key / Signature / Fingerprint / Certificate..."
multiline
rows="8"
/>
</c-card>

<c-input-text
v-model:value="passphrase"
label="Passphrase (for encrypted keys):"
placeholder="Passphrase (for encrypted keys)..."
type="password"
/>

<n-divider />

<input-copyable
v-for="{ label, value, multiline } of parsedSections"
:key="label"
:label="label"
label-position="left"
label-width="100px"
label-align="right"
mb-2
autosize
:multiline="multiline"
:value="value"
placeholder="Not Set"
/>

<div flex justify-center v-if="certificateX509DER !== ''">
<c-button @click="downloadX509DERFile()">
Download X509 DER certificate
</c-button>
</div>
</div>
</template>
12 changes: 12 additions & 0 deletions src/tools/certificate-key-parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FileCertificate } from '@vicons/tabler';
import { defineTool } from '../tool';

export const tool = defineTool({
name: 'Certificate/Key parser',
path: '/certificate-key-parser',
description: 'Parse Key and Certificate',
keywords: ['certificate', 'key', 'parser'],
component: () => import('./certificate-key-parser.vue'),
icon: FileCertificate,
createdAt: new Date('2024-02-22'),
});
Loading

0 comments on commit 4adff00

Please sign in to comment.