馃 An object-oriented Gemini server for Deno!
gemini-protocol
deno
typescript
gemini
1// This file is part of Laurali <https://github.com/gemrest/laurali>.
2// Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, version 3.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11// General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15//
16// Copyright (C) 2022-2022 Fuwn <contact@fuwn.me>
17// SPDX-License-Identifier: GPL-3.0-only
18
19/** An OpenSSL keypair config for use with `generateKey` */
20export interface KeyConfig {
21 /**
22 * The subject of an OpenSSL keypair
23 * @default "CN=localhost"
24 */
25 subj?: string;
26 /**
27 * The elliptic curve of an OpenSSL keypair
28 * @default "secp256k1"
29 */
30 ec_paramgen_curve?: string;
31 /**
32 * The lifetime in days of an OpenSSL keypair
33 * @default "365"
34 */
35 days?: string;
36 /** The output file of an OpenSSL public key */
37 out: string;
38 /** The output file of an OpenSSL private key */
39 keyOut: string;
40 /**
41 * The format of an OpenSSL keypair
42 * @default "pem"
43 */
44 format?: string;
45}
46
47/**
48 * Generate an OpenSSL keypair
49 * @param config The configuration of the OpenSSL keypair
50 */
51export const generateKey = async (
52 config: KeyConfig,
53): Promise<Deno.ProcessStatus> => {
54 return await Deno.run({
55 cmd: [
56 "openssl",
57 "req",
58 "-new",
59 "-subj",
60 config.subj || "/CN=localhost",
61 "-x509",
62 "-newkey",
63 "ec",
64 "-pkeyopt",
65 `ec_paramgen_curve:${config.ec_paramgen_curve || "secp384r1"}`,
66 "-days",
67 config.days || "365",
68 "-nodes",
69 "-out",
70 config.out,
71 "-keyout",
72 config.keyOut,
73 "-inform",
74 config.format || "pem",
75 ],
76 stdout: "piped",
77 }).status();
78};
79
80/** https://stackoverflow.com/a/61868755/14452787 */
81const exists = async (filename: string): Promise<boolean> => {
82 try {
83 await Deno.stat(filename);
84
85 return true;
86 } catch (e) {
87 if (e instanceof Deno.errors.NotFound) {
88 return false;
89 } else {
90 throw e;
91 }
92 }
93};
94
95if (import.meta.main) {
96 const { parse } = await import("https://deno.land/std@0.139.0/flags/mod.ts");
97
98 await Deno.mkdir(".laurali", { recursive: true });
99
100 const generate = async () => {
101 await generateKey({
102 subj: `/CN=${
103 prompt(
104 "Which common name (hostname) would you like to use for the " +
105 "generated key?",
106 )
107 }`,
108 out: `.laurali/public.pem`,
109 keyOut: `.laurali/private.pem`,
110 });
111 };
112
113 if (
114 (await exists(".laurali/public.pem") ||
115 await exists(".laurali/private.pem")) &&
116 !parse(Deno.args, { "boolean": true }).overwrite
117 ) {
118 const overwrite = prompt(
119 "鈿狅笍 锔廇 file already exists at .laurali/public.pem or" +
120 ".laurali/private.pem.\n Overwrite? [y/n (y = yes overwrite, n = " +
121 "no keep)]",
122 );
123
124 if (overwrite === "y") {
125 generate();
126 } else {
127 throw new Error(
128 "Requires overwrite permissions to file(s), run again with " +
129 "the --overwrite flag",
130 );
131 }
132 } else {
133 generate();
134 }
135}