Skip to content

Commit aa0f705

Browse files
committed
feat(new tool): PGP Keygen
Fix part of CorentinTh#945 and part of CorentinTh#451
1 parent 9eac9cb commit aa0f705

File tree

6 files changed

+201
-13
lines changed

6 files changed

+201
-13
lines changed

components.d.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,27 +130,24 @@ declare module '@vue/runtime-core' {
130130
NCode: typeof import('naive-ui')['NCode']
131131
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
132132
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
133-
NDivider: typeof import('naive-ui')['NDivider']
134133
NEllipsis: typeof import('naive-ui')['NEllipsis']
135134
NFormItem: typeof import('naive-ui')['NFormItem']
136-
NGi: typeof import('naive-ui')['NGi']
137-
NGrid: typeof import('naive-ui')['NGrid']
138135
NH1: typeof import('naive-ui')['NH1']
139136
NH3: typeof import('naive-ui')['NH3']
140137
NIcon: typeof import('naive-ui')['NIcon']
138+
NInput: typeof import('naive-ui')['NInput']
141139
NInputNumber: typeof import('naive-ui')['NInputNumber']
142-
NLabel: typeof import('naive-ui')['NLabel']
143140
NLayout: typeof import('naive-ui')['NLayout']
144141
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
145142
NMenu: typeof import('naive-ui')['NMenu']
146143
NScrollbar: typeof import('naive-ui')['NScrollbar']
147-
NSpin: typeof import('naive-ui')['NSpin']
148144
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149145
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
150146
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
151147
PdfSignatureChecker: typeof import('./src/tools/pdf-signature-checker/pdf-signature-checker.vue')['default']
152148
PdfSignatureDetails: typeof import('./src/tools/pdf-signature-checker/components/pdf-signature-details.vue')['default']
153149
PercentageCalculator: typeof import('./src/tools/percentage-calculator/percentage-calculator.vue')['default']
150+
PgpKeygen: typeof import('./src/tools/pgp-keygen/pgp-keygen.vue')['default']
154151
PhoneParserAndFormatter: typeof import('./src/tools/phone-parser-and-formatter/phone-parser-and-formatter.vue')['default']
155152
QrCodeGenerator: typeof import('./src/tools/qr-code-generator/qr-code-generator.vue')['default']
156153
RandomPortGenerator: typeof import('./src/tools/random-port-generator/random-port-generator.vue')['default']
@@ -159,6 +156,7 @@ declare module '@vue/runtime-core' {
159156
RouterLink: typeof import('vue-router')['RouterLink']
160157
RouterView: typeof import('vue-router')['RouterView']
161158
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
159+
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
162160
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
163161
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
164162
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"naive-ui": "^2.35.0",
7676
"netmask": "^2.0.2",
7777
"node-forge": "^1.3.1",
78+
"openpgp": "^5.11.1",
7879
"oui-data": "^1.0.10",
7980
"pdf-signature-reader": "^1.4.2",
8081
"pinia": "^2.0.34",

pnpm-lock.yaml

Lines changed: 34 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tools/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { tool as asciiTextDrawer } from './ascii-text-drawer';
66

77
import { tool as textToUnicode } from './text-to-unicode';
88
import { tool as safelinkDecoder } from './safelink-decoder';
9+
import { tool as pgpKeygen } from './pgp-keygen';
910
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
1011
import { tool as numeronymGenerator } from './numeronym-generator';
1112
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -85,7 +86,20 @@ import { tool as yamlViewer } from './yaml-viewer';
8586
export const toolsByCategory: ToolCategory[] = [
8687
{
8788
name: 'Crypto',
88-
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker],
89+
components: [
90+
tokenGenerator,
91+
hashText,
92+
bcrypt,
93+
uuidGenerator,
94+
ulidGenerator,
95+
cypher,
96+
bip39,
97+
hmacGenerator,
98+
rsaKeyPairGenerator,
99+
passwordStrengthAnalyser,
100+
pdfSignatureChecker,
101+
pgpKeygen,
102+
],
89103
},
90104
{
91105
name: 'Converter',

src/tools/pgp-keygen/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { Certificate } from '@vicons/tabler';
2+
import { defineTool } from '../tool';
3+
4+
export const tool = defineTool({
5+
name: 'PGP keygen',
6+
path: '/pgp-key-pair-generator',
7+
description: 'Generate new random PGP private and public keys (with or without passphrase).',
8+
keywords: ['pgp', 'key', 'pair', 'generator', 'public', 'private', 'secret', 'ssh', 'pem', 'passphrase', 'password'],
9+
component: () => import('./pgp-keygen.vue'),
10+
icon: Certificate,
11+
createdAt: new Date('2024-04-20'),
12+
});

src/tools/pgp-keygen/pgp-keygen.vue

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<script setup lang="ts">
2+
import * as openpgp from 'openpgp';
3+
import TextareaCopyable from '@/components/TextareaCopyable.vue';
4+
import { useValidation } from '@/composable/validation';
5+
import { computedRefreshableAsync } from '@/composable/computedRefreshable';
6+
7+
const bits = ref(4096);
8+
const username = ref('');
9+
const useremail = ref('');
10+
const password = ref('');
11+
12+
const format = useStorage('pgp-key-pair-generator:format', 'curve25519');
13+
const formats = [
14+
'rsa',
15+
'curve25519',
16+
'ed25519',
17+
'p256', 'p384', 'p521',
18+
'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1',
19+
'secp256k1',
20+
];
21+
22+
const { attrs: bitsValidationAttrs } = useValidation({
23+
source: bits,
24+
rules: [
25+
{
26+
message: 'Bits should be 256 <= bits <= 16384 and be a multiple of 8',
27+
validator: value => value >= 256 && value <= 16384 && value % 8 === 0,
28+
},
29+
],
30+
});
31+
32+
const [certs, refreshCerts] = computedRefreshableAsync(
33+
async () => {
34+
const formatValue = format.value;
35+
const passphrase = password.value;
36+
const name = username.value;
37+
const email = useremail.value;
38+
const rsaBits = bits.value;
39+
40+
try {
41+
if (format.value === 'rsa') {
42+
const { privateKey, publicKey } = await openpgp.generateKey({
43+
type: 'rsa', // Type of the key
44+
rsaBits, // RSA key size (defaults to 4096 bits)
45+
userIDs: [{ name, email }], // you can pass multiple user IDs
46+
passphrase, // protects the private key
47+
format: 'armored',
48+
});
49+
50+
return { privateKey, publicKey, revocationCertificate: '' };
51+
}
52+
53+
const { privateKey, publicKey, revocationCertificate } = await openpgp.generateKey({
54+
type: 'ecc', // Type of the key, defaults to ECC
55+
curve: formatValue as openpgp.EllipticCurveName, // ECC curve name, defaults to curve25519
56+
userIDs: [{ name, email }], // you can pass multiple user IDs
57+
passphrase, // protects the private key
58+
format: 'armored',
59+
});
60+
return { privateKey, publicKey, revocationCertificate };
61+
}
62+
catch (e: any) {
63+
return { privateKey: `#${e.toString()}`, publicKey: `#${e.toString()}`, revocationCertificate: '' };
64+
}
65+
}, { privateKey: '', publicKey: '', revocationCertificate: '' },
66+
);
67+
</script>
68+
69+
<template>
70+
<div>
71+
<div mb-4>
72+
<div style="flex: 0 0 100%">
73+
<div item-style="flex: 1 1 0" style="max-width: 600px" mx-auto flex gap-3>
74+
<c-select
75+
v-model:value="format"
76+
label-position="left"
77+
label="Format:"
78+
:options="formats"
79+
placeholder="Select a key format"
80+
/>
81+
82+
<n-form-item v-if="format === 'rsa'" label="RSA Bits :" v-bind="bitsValidationAttrs as any" label-placement="left">
83+
<n-input-number v-model:value="bits" min="256" max="16384" step="8" />
84+
</n-form-item>
85+
</div>
86+
</div>
87+
</div>
88+
89+
<div>
90+
<n-form-item label="User Name :" label-placement="left">
91+
<n-input
92+
v-model:value="username"
93+
type="text"
94+
placeholder="User Name"
95+
/>
96+
</n-form-item>
97+
<n-form-item label="User Email :" label-placement="left">
98+
<n-input
99+
v-model:value="useremail"
100+
:input-props="{ type: 'email' }"
101+
placeholder="User Email"
102+
/>
103+
</n-form-item>
104+
105+
<n-form-item label="Passphrase :" label-placement="left">
106+
<n-input
107+
v-model:value="password"
108+
type="password"
109+
show-password-on="mousedown"
110+
placeholder="Passphrase"
111+
/>
112+
</n-form-item>
113+
114+
<div text-center>
115+
<c-button @click="refreshCerts">
116+
Refresh key-pair
117+
</c-button>
118+
</div>
119+
</div>
120+
121+
<div>
122+
<h3>Public key</h3>
123+
<TextareaCopyable :value="certs?.publicKey || ''" word-wrap="true" />
124+
</div>
125+
126+
<div>
127+
<h3>Private key</h3>
128+
<TextareaCopyable :value="certs?.privateKey || ''" word-wrap="true" />
129+
</div>
130+
131+
<div>
132+
<h3>Revocation Certificate</h3>
133+
<TextareaCopyable :value="certs?.revocationCertificate || ''" word-wrap="true" />
134+
</div>
135+
</div>
136+
</template>

0 commit comments

Comments
 (0)