aboutsummaryrefslogtreecommitdiff
path: root/tools/genssl
blob: a390189cb442534f0f98927ec1c7a7b6816acee2 (about) (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#!/usr/bin/env perl
#
# InspIRCd -- Internet Relay Chat Daemon
#
#   Copyright (C) 2025 Sadie Powell <sadie@witchery.services>
#
# This file is part of InspIRCd.  InspIRCd is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#


use v5.26.0;
use strict;
use warnings FATAL => qw(all);

use Cwd        qw(getcwd);
use File::Temp ();

# IMPORTANT: This script has to be able to run by itself so that it can be used
#            by binary distributions where the make/console.pm module will not
#            be available!
eval {
	use File::Basename qw(dirname);
	use FindBin        qw($RealDir);

	use lib dirname $RealDir;
	require make::console;
	make::console->import();
};

sub prompt($$) {
	my ($question, $default) = @_;
	return prompt_string(1, $question, $default) if defined main->can('prompt_string');
	say $question;
	print "[$default] => ";
	chomp(my $answer = <STDIN>);
	say '';
	return $answer ? $answer : $default;
}

if (scalar @ARGV < 1 || $ARGV[0] !~ /^(?:auto|gnutls|openssl)$/i) {
	say STDERR "Usage: $0 <auto|gnutls|openssl> [SSL-DIR]";
	exit 1;
}

# On OS X the GnuTLS certtool is prefixed to avoid collision with the system certtool.
my $certtool = $^O eq 'darwin' ? 'gnutls-certtool' : 'certtool';

# Check whether the user has the required tools installed.
my $has_gnutls = !system "$certtool --help 2>/dev/null";
my $has_openssl = !system 'openssl version >/dev/null 2>&1';

# The framework the user has specified.
my $tool = lc $ARGV[0];

# If the user has not explicitly specified a framework then detect one.
if ($tool eq 'auto') {
	if ($has_gnutls) {
		$tool = 'gnutls';
	} elsif ($has_openssl) {
		$tool = 'openssl';
	} else {
		say STDERR "SSL generation failed: could not find $certtool or openssl in the PATH!";
		exit 1;
	}
} elsif ($tool eq 'gnutls' && !$has_gnutls) {
	say STDERR "SSL generation failed: could not find '$certtool' in the PATH!";
	exit 1;
} elsif ($tool eq 'openssl' && !$has_openssl) {
	say STDERR 'SSL generation failed: could not find \'openssl\' in the PATH!';
	exit 1;
}

# Output to the cwd unless an SSL directory is specified.
if (scalar @ARGV > 1 && !chdir $ARGV[1]) {
	say STDERR "Unable to change the working directory to $ARGV[1]: $!.";
	exit 1;
}

# Harvest information needed to generate the certificate.
my $common_name = prompt('What is the hostname of your server?', 'irc.example.com');
my $email = prompt('What email address can you be contacted at?', 'example@example.com');
my $unit = prompt('What is the name of your unit?', 'Server Admins');
my $organization = prompt('What is the name of your organization?', 'Example IRC Network');
my $city = prompt('What city are you located in?', 'Example City');
my $state = prompt('What state are you located in?', 'Example State');
my $country = prompt('What is the ISO 3166-1 code for the country you are located in?', 'XZ');
my $days = prompt('How many days do you want your certificate to be valid for?', '365');

# Contains the exit code of openssl/gnutls-certtool.
my $status = 0;

if ($tool eq 'gnutls') {
	my $tmp = new File::Temp();
	print $tmp <<__GNUTLS_END__;
cn              = "$common_name"
email           = "$email"
unit            = "$unit"
organization    = "$organization"
locality        = "$city"
state           = "$state"
country         = "$country"
expiration_days = $days
tls_www_client
tls_www_server
__GNUTLS_END__
	close($tmp);
	$status ||= system "$certtool --generate-privkey --sec-param normal --outfile key.pem";
	$status ||= system "$certtool --generate-self-signed --load-privkey key.pem --outfile cert.pem --template $tmp";
} elsif ($tool eq 'openssl') {
	my $tmp = new File::Temp();
	print $tmp <<__OPENSSL_END__;
$country
$state
$city
$organization
$unit
$common_name
$email
.
$organization
__OPENSSL_END__
	close($tmp);
	$status ||= system "cat $tmp | openssl req -x509 -nodes -newkey rsa:2048 -keyout key.pem -out cert.pem -days $days 2>/dev/null";
}

if ($status) {
	say STDERR "SSL generation failed: $tool exited with a non-zero status!";
	exit 1;
} else {
	say <<~"EOM";
	An SSL certificate and key have been generated and placed in ${\getcwd()}.

	NOTE: It is *NOT SECURE* to use these files for public client connections. They
	are intended for server connections where the fingerprint is pinned in the link
	config. For public client connections you should obtain a certificate and key
	from a trusted authority like Let's Encrypt. This can be automated using a tool
	like Certbot. See https://certbot.eff.org/instructions for more details.
	EOM
}