Skip to content

Commit ca44c5c

Browse files
committed
feat(new tool): Ed25519 Key Pair Generator
Fix CorentinTh#281 (ed25519 key pairs, passphrase and openssh format) Supports generation in PEM, PKCS#8, OpenSSH Standard (with or without passphrase), OpenSSH (new format) and PuTTY (no passphrase)
1 parent 60841f6 commit ca44c5c

File tree

5 files changed

+138
-47
lines changed

5 files changed

+138
-47
lines changed

components.d.ts

Lines changed: 1 addition & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ declare module '@vue/runtime-core' {
7070
DiffViewer: typeof import('./src/tools/json-diff/diff-viewer/diff-viewer.vue')['default']
7171
DockerRunToDockerComposeConverter: typeof import('./src/tools/docker-run-to-docker-compose-converter/docker-run-to-docker-compose-converter.vue')['default']
7272
DynamicValues: typeof import('./src/tools/benchmark-builder/dynamic-values.vue')['default']
73+
Ed25519KeyPairGenerator: typeof import('./src/tools/ed25519-key-pair-generator/ed25519-key-pair-generator.vue')['default']
7374
Editor: typeof import('./src/tools/html-wysiwyg-editor/editor/editor.vue')['default']
7475
EmojiCard: typeof import('./src/tools/emoji-picker/emoji-card.vue')['default']
7576
EmojiGrid: typeof import('./src/tools/emoji-picker/emoji-grid.vue')['default']
@@ -88,28 +89,9 @@ declare module '@vue/runtime-core' {
8889
HttpStatusCodes: typeof import('./src/tools/http-status-codes/http-status-codes.vue')['default']
8990
IbanValidatorAndParser: typeof import('./src/tools/iban-validator-and-parser/iban-validator-and-parser.vue')['default']
9091
'IconMdi:brushVariant': typeof import('~icons/mdi/brush-variant')['default']
91-
'IconMdi:contentCopy': typeof import('~icons/mdi/content-copy')['default']
9292
'IconMdi:kettleSteamOutline': typeof import('~icons/mdi/kettle-steam-outline')['default']
93-
IconMdiArrowDown: typeof import('~icons/mdi/arrow-down')['default']
94-
IconMdiArrowRightBottom: typeof import('~icons/mdi/arrow-right-bottom')['default']
95-
IconMdiCamera: typeof import('~icons/mdi/camera')['default']
96-
IconMdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
97-
IconMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
98-
IconMdiClose: typeof import('~icons/mdi/close')['default']
9993
IconMdiContentCopy: typeof import('~icons/mdi/content-copy')['default']
100-
IconMdiDeleteOutline: typeof import('~icons/mdi/delete-outline')['default']
101-
IconMdiDownload: typeof import('~icons/mdi/download')['default']
102-
IconMdiEye: typeof import('~icons/mdi/eye')['default']
103-
IconMdiEyeOff: typeof import('~icons/mdi/eye-off')['default']
104-
IconMdiHeart: typeof import('~icons/mdi/heart')['default']
105-
IconMdiPause: typeof import('~icons/mdi/pause')['default']
106-
IconMdiPlay: typeof import('~icons/mdi/play')['default']
107-
IconMdiRecord: typeof import('~icons/mdi/record')['default']
108-
IconMdiRefresh: typeof import('~icons/mdi/refresh')['default']
109-
IconMdiSearch: typeof import('~icons/mdi/search')['default']
11094
IconMdiTranslate: typeof import('~icons/mdi/translate')['default']
111-
IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default']
112-
IconMdiVideo: typeof import('~icons/mdi/video')['default']
11395
InputCopyable: typeof import('./src/components/InputCopyable.vue')['default']
11496
IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default']
11597
Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default']
@@ -136,39 +118,12 @@ declare module '@vue/runtime-core' {
136118
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
137119
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
138120
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
139-
NAlert: typeof import('naive-ui')['NAlert']
140121
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
141-
NCheckbox: typeof import('naive-ui')['NCheckbox']
142-
NCode: typeof import('naive-ui')['NCode']
143-
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
144-
NColorPicker: typeof import('naive-ui')['NColorPicker']
145122
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
146-
NDatePicker: typeof import('naive-ui')['NDatePicker']
147-
NDivider: typeof import('naive-ui')['NDivider']
148-
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
149123
NEllipsis: typeof import('naive-ui')['NEllipsis']
150-
NForm: typeof import('naive-ui')['NForm']
151-
NFormItem: typeof import('naive-ui')['NFormItem']
152-
NGi: typeof import('naive-ui')['NGi']
153-
NGrid: typeof import('naive-ui')['NGrid']
154124
NH1: typeof import('naive-ui')['NH1']
155-
NH2: typeof import('naive-ui')['NH2']
156125
NH3: typeof import('naive-ui')['NH3']
157126
NIcon: typeof import('naive-ui')['NIcon']
158-
NImage: typeof import('naive-ui')['NImage']
159-
NInputGroup: typeof import('naive-ui')['NInputGroup']
160-
NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel']
161-
NInputNumber: typeof import('naive-ui')['NInputNumber']
162-
NLayout: typeof import('naive-ui')['NLayout']
163-
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
164-
NMenu: typeof import('naive-ui')['NMenu']
165-
NProgress: typeof import('naive-ui')['NProgress']
166-
NScrollbar: typeof import('naive-ui')['NScrollbar']
167-
NSlider: typeof import('naive-ui')['NSlider']
168-
NStatistic: typeof import('naive-ui')['NStatistic']
169-
NSwitch: typeof import('naive-ui')['NSwitch']
170-
NTable: typeof import('naive-ui')['NTable']
171-
NTag: typeof import('naive-ui')['NTag']
172127
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
173128
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
174129
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import sshpk from 'sshpk';
2+
3+
export { generateKeyPair };
4+
5+
async function generateKeyPair(config: {
6+
password?: string
7+
format?: sshpk.PrivateKeyFormatType
8+
comment?: string
9+
} = {}) {
10+
const privKey = sshpk.generatePrivateKey('ed25519');
11+
privKey.comment = config?.comment;
12+
13+
const pubFormat = config.format ?? 'ssh';
14+
let privFormat: sshpk.PrivateKeyFormatType = config.format ?? 'ssh';
15+
if (privFormat === 'ssh') {
16+
privFormat = 'ssh-private';
17+
}
18+
const pubKey = privKey.toPublic();
19+
return {
20+
publicKey: pubKey.toString(pubFormat),
21+
privateKey: config?.password
22+
? privKey.toString(privFormat,
23+
{
24+
passphrase: config?.password,
25+
comment: config?.comment,
26+
},
27+
)
28+
: privKey.toString(privFormat, { comment: config?.comment }),
29+
};
30+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<script setup lang="ts">
2+
import type sshpk from 'sshpk';
3+
import { generateKeyPair } from './ed25519-key-pair-generator.service';
4+
import TextareaCopyable from '@/components/TextareaCopyable.vue';
5+
import { withDefaultOnErrorAsync } from '@/utils/defaults';
6+
import { computedRefreshableAsync } from '@/composable/computedRefreshable';
7+
8+
const password = ref('');
9+
const comment = ref('');
10+
const emptyCerts = { publicKey: '', privateKey: '' };
11+
12+
const format = useStorage('ed25519-key-pair-generator:format', 'ssh');
13+
const formatOptions = [
14+
{ value: 'pem', label: 'PEM' },
15+
{ value: 'pkcs8', label: 'PKCS#8' },
16+
{ value: 'ssh', label: 'OpenSSH Standard' },
17+
{ value: 'openssh', label: 'OpenSSH New' },
18+
{ value: 'putty', label: 'PuTTY' },
19+
];
20+
21+
const supportsPassphrase = computed(() => format.value === 'ssh');
22+
23+
const [certs, refreshCerts] = computedRefreshableAsync(
24+
() => withDefaultOnErrorAsync(() => generateKeyPair(
25+
{
26+
password: password.value,
27+
format: format.value as sshpk.PrivateKeyFormatType,
28+
comment: comment.value,
29+
},
30+
), emptyCerts),
31+
emptyCerts,
32+
);
33+
</script>
34+
35+
<template>
36+
<div>
37+
<n-space mb-2>
38+
<c-select
39+
v-model:value="format"
40+
label-position="left"
41+
label="Format:"
42+
:options="formatOptions"
43+
placeholder="Select a key format"
44+
/>
45+
46+
<n-form-item v-if="supportsPassphrase" label="Passphrase :" label-placement="left">
47+
<n-input
48+
v-model:value="password"
49+
type="password"
50+
show-password-on="mousedown"
51+
placeholder="Passphrase"
52+
/>
53+
</n-form-item>
54+
</n-space>
55+
56+
<n-space mb-2>
57+
<n-form-item label="Comment :" label-placement="left">
58+
<n-input
59+
v-model:value="comment"
60+
type="text"
61+
placeholder="Comment"
62+
/>
63+
</n-form-item>
64+
65+
<c-button @click="refreshCerts">
66+
Refresh key-pair
67+
</c-button>
68+
</n-space>
69+
70+
<div>
71+
<h3>Public key</h3>
72+
<TextareaCopyable :value="certs.publicKey" :word-wrap="true" />
73+
</div>
74+
75+
<div>
76+
<h3>Private key</h3>
77+
<TextareaCopyable :value="certs.privateKey" />
78+
</div>
79+
</div>
80+
</template>
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: 'Ed25519 key pair generator',
6+
path: '/ed25519-key-pair-generator',
7+
description: 'Generate new random Ed25519 private and public keys (with or without passphrase).',
8+
keywords: ['ed25519', 'key', 'pair', 'generator', 'public', 'private', 'secret', 'ssh', 'pem', 'passphrase', 'password'],
9+
component: () => import('./ed25519-key-pair-generator.vue'),
10+
icon: Certificate,
11+
createdAt: new Date('2024-02-14'),
12+
});

src/tools/index.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
22
import { tool as base64StringConverter } from './base64-string-converter';
33
import { tool as basicAuthGenerator } from './basic-auth-generator';
44
import { tool as textToUnicode } from './text-to-unicode';
5+
import { tool as ed25519KeyPairGenerator } from './ed25519-key-pair-generator';
56
import { tool as pdfSignatureChecker } from './pdf-signature-checker';
67
import { tool as numeronymGenerator } from './numeronym-generator';
78
import { tool as macAddressGenerator } from './mac-address-generator';
@@ -81,7 +82,20 @@ import { tool as yamlViewer } from './yaml-viewer';
8182
export const toolsByCategory: ToolCategory[] = [
8283
{
8384
name: 'Crypto',
84-
components: [tokenGenerator, hashText, bcrypt, uuidGenerator, ulidGenerator, cypher, bip39, hmacGenerator, rsaKeyPairGenerator, passwordStrengthAnalyser, pdfSignatureChecker],
85+
components: [
86+
tokenGenerator,
87+
hashText,
88+
bcrypt,
89+
uuidGenerator,
90+
ulidGenerator,
91+
cypher,
92+
bip39,
93+
hmacGenerator,
94+
rsaKeyPairGenerator,
95+
ed25519KeyPairGenerator,
96+
passwordStrengthAnalyser,
97+
pdfSignatureChecker,
98+
],
8599
},
86100
{
87101
name: 'Converter',

0 commit comments

Comments
 (0)