Skip to content

Commit 95a83fe

Browse files
committed
Merge branch 'feat/pgp-encryption' into chore/all-my-stuffs
# Conflicts: # components.d.ts # pnpm-lock.yaml # src/tools/index.ts
2 parents 8ebda99 + 01a0e06 commit 95a83fe

File tree

5 files changed

+230
-2
lines changed

5 files changed

+230
-2
lines changed

pnpm-lock.yaml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/composable/computed/catchedComputed.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type Ref, ref, watchEffect } from 'vue';
22

3-
export { computedCatch };
3+
export { computedCatch, computedCatchAsync };
44

55
function computedCatch<T, D>(getter: () => T, { defaultValue }: { defaultValue: D; defaultErrorMessage?: string }): [Ref<T | D>, Ref<string | undefined>];
66
function computedCatch<T, D>(getter: () => T, { defaultValue, defaultErrorMessage = 'Unknown error' }: { defaultValue?: D; defaultErrorMessage?: string } = {}) {
@@ -20,3 +20,22 @@ function computedCatch<T, D>(getter: () => T, { defaultValue, defaultErrorMessag
2020

2121
return [value, error] as const;
2222
}
23+
24+
function computedCatchAsync<T, D>(getterAsync: () => Promise<T>, { defaultValue }: { defaultValue: D; defaultErrorMessage?: string }): [Ref<T | D>, Ref<string | undefined>];
25+
function computedCatchAsync<T, D>(getterAsync: () => Promise<T>, { defaultValue, defaultErrorMessage = 'Unknown error' }: { defaultValue?: D; defaultErrorMessage?: string } = {}) {
26+
const error = ref<string | undefined>();
27+
const value = ref<T | D | undefined>();
28+
29+
watchEffect(async () => {
30+
try {
31+
error.value = undefined;
32+
value.value = await getterAsync();
33+
}
34+
catch (err) {
35+
error.value = err instanceof Error ? err.message : err?.toString() ?? defaultErrorMessage;
36+
value.value = defaultValue;
37+
}
38+
});
39+
40+
return [value, error] as const;
41+
}

src/tools/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ import { tool as markdownCheatsheet } from './markdown-cheatsheet';
127127
import { tool as markdownEditor } from './markdown-editor';
128128
import { tool as nanoMemo } from './nano-memo';
129129
import { tool as option43Generator } from './option43-generator';
130+
import { tool as pgpEncryption } from './pgp-encryption';
130131
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
131132
import { tool as numeronymGenerator } from './numeronym-generator';
132133
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -270,6 +271,9 @@ export const toolsByCategory: ToolCategory[] = [
270271
ed25519KeyPairGenerator,
271272
passwordStrengthAnalyser,
272273
pdfSignatureChecker,
274+
passwordStrengthAnalyser,
275+
pdfSignatureChecker,
276+
pgpEncryption,
273277
],
274278
},
275279
{

src/tools/pgp-encryption/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Lock } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'PGP encryption',
6+
path: '/pgp-encryption',
7+
description: 'Encrypt and decrypt text clear text using PGP Keys.',
8+
keywords: ['pgp', 'openpgp', 'encryption', 'cypher', 'encipher', 'crypt', 'decrypt'],
9+
component: () => import('./pgp-encryption.vue'),
10+
icon: Lock,
11+
createdAt: new Date('2024-04-20'),
12+
});
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
<script setup lang="ts">
2+
import * as openpgp from 'openpgp';
3+
import { computedCatchAsync } from '@/composable/computed/catchedComputed';
4+
5+
const cryptInput = ref('');
6+
const cryptPublicKey = ref('');
7+
const cryptPrivateKey = ref('');
8+
const cryptPrivateKeyPassphrase = ref('');
9+
const [cryptOutput, cryptError] = computedCatchAsync(async () => {
10+
const publicKeyArmored = cryptPublicKey.value;
11+
const privateKeyArmored = cryptPrivateKey.value;
12+
const passphrase = cryptPrivateKeyPassphrase.value;
13+
const text = cryptInput.value;
14+
15+
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
16+
17+
const privateKey = privateKeyArmored !== ''
18+
? (passphrase !== ''
19+
? await openpgp.decryptKey({
20+
privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
21+
passphrase,
22+
})
23+
: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }))
24+
: undefined;
25+
26+
return await openpgp.encrypt({
27+
message: await openpgp.createMessage({ text }),
28+
encryptionKeys: publicKey,
29+
signingKeys: privateKey,
30+
});
31+
}, {
32+
defaultValue: '',
33+
defaultErrorMessage: 'Unable to encrypt your text',
34+
});
35+
36+
const decryptInput = ref('');
37+
const decryptPublicKey = ref('');
38+
const decryptPrivateKey = ref('');
39+
const decryptPrivateKeyPassphrase = ref('');
40+
const [decryptOutput, decryptError] = computedCatchAsync(async () => {
41+
const publicKeyArmored = decryptPublicKey.value;
42+
const privateKeyArmored = decryptPrivateKey.value;
43+
const passphrase = decryptPrivateKeyPassphrase.value;
44+
const encrypted = decryptInput.value;
45+
46+
const publicKey = publicKeyArmored !== '' ? await openpgp.readKey({ armoredKey: publicKeyArmored }) : undefined;
47+
48+
const privateKey = passphrase !== ''
49+
? await openpgp.decryptKey({
50+
privateKey: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored }),
51+
passphrase,
52+
})
53+
: await openpgp.readPrivateKey({ armoredKey: privateKeyArmored });
54+
55+
const message = await openpgp.readMessage({
56+
armoredMessage: encrypted, // parse armored message
57+
});
58+
const { data: decrypted, signatures } = await openpgp.decrypt({
59+
message,
60+
verificationKeys: publicKey, // optional
61+
decryptionKeys: privateKey,
62+
});
63+
if (signatures.length > 0) {
64+
try {
65+
await signatures[0].verified; // throws on invalid signature
66+
}
67+
catch (e: any) {
68+
return {
69+
decryptedText: decrypted,
70+
signatureError: `Signature could not be verified: ${e.toString()}`,
71+
};
72+
}
73+
}
74+
return {
75+
decryptedText: decrypted,
76+
signatureError: '',
77+
};
78+
}, {
79+
defaultValue: { decryptedText: '', signatureError: '' },
80+
defaultErrorMessage: 'Unable to encrypt your text',
81+
});
82+
</script>
83+
84+
<template>
85+
<div>
86+
<c-card title="Encrypt">
87+
<div>
88+
<c-input-text
89+
v-model:value="cryptInput"
90+
label="Your text:"
91+
placeholder="The string to encrypt"
92+
rows="4"
93+
multiline raw-text monospace autosize flex-1
94+
/>
95+
<div flex flex-1 flex-col gap-2>
96+
<c-input-text
97+
v-model:value="cryptPublicKey"
98+
label="Target public key:"
99+
placeholder="Target public key"
100+
rows="5"
101+
multiline raw-text monospace autosize flex-1
102+
/>
103+
104+
<details>
105+
<summary>Signing private key (optional)</summary>
106+
<c-input-text
107+
v-model:value="cryptPrivateKey"
108+
label="Your private key:"
109+
placeholder="The private key to use to sign message"
110+
rows="5"
111+
multiline raw-text monospace autosize flex-1
112+
/>
113+
114+
<c-input-text
115+
v-model:value="cryptPrivateKeyPassphrase"
116+
label="Your private key password:" clearable raw-text
117+
/>
118+
</details>
119+
</div>
120+
</div>
121+
122+
<c-alert
123+
v-if="cryptError && cryptPublicKey !== ''"
124+
type="error" mt-12 title="Error while encrypting"
125+
>
126+
{{ cryptError }}
127+
</c-alert>
128+
129+
<n-form-item label="Your text encrypted:" mt-3>
130+
<TextareaCopyable
131+
:value="cryptOutput || ''"
132+
rows="3"
133+
placeholder="Your string encrypted"
134+
multiline monospace readonly autosize mt-5
135+
/>
136+
</n-form-item>
137+
</c-card>
138+
139+
<c-card title="Decrypt">
140+
<div>
141+
<c-input-text
142+
v-model:value="decryptInput"
143+
label="Your PGP Message to decrypt:"
144+
placeholder="The string to decrypt"
145+
rows="4"
146+
multiline raw-text monospace autosize flex-1
147+
/>
148+
<div flex flex-1 flex-col gap-2>
149+
<c-input-text
150+
v-model:value="decryptPrivateKey"
151+
label="Your private key:"
152+
placeholder="The private key to use to decrypt message"
153+
rows="5"
154+
multiline raw-text monospace autosize flex-1
155+
/>
156+
157+
<c-input-text
158+
v-model:value="decryptPrivateKeyPassphrase"
159+
label="Your private key password:" clearable raw-text
160+
/>
161+
162+
<details>
163+
<summary>Signing public key (optional)</summary>
164+
165+
<c-input-text
166+
v-model:value="decryptPublicKey"
167+
label="Sender public key:"
168+
placeholder="Sender public key"
169+
rows="5"
170+
multiline raw-text monospace autosize flex-1
171+
/>
172+
</details>
173+
</div>
174+
</div>
175+
176+
<c-alert v-if="decryptError && decryptPrivateKey !== ''" type="error" mt-3 title="Error while decrypting">
177+
{{ decryptError }}
178+
</c-alert>
179+
180+
<c-alert v-if="decryptOutput?.signatureError !== ''" type="error" mt-3 title="Signature verification">
181+
{{ decryptOutput?.signatureError }}
182+
</c-alert>
183+
184+
<n-form-item label="Your text decrypted:" mt-3>
185+
<TextareaCopyable
186+
:value="decryptOutput?.decryptedText || ''"
187+
rows="3"
188+
placeholder="Your string decrypted"
189+
multiline monospace readonly autosize mt-5
190+
/>
191+
</n-form-item>
192+
</c-card>
193+
</div>
194+
</template>

0 commit comments

Comments
 (0)